aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--anttasks/src/com/android/ant/AaptExecLoopTask.java110
-rw-r--r--anttasks/src/com/android/ant/BaseTask.java70
-rw-r--r--anttasks/src/com/android/ant/DependencyGraph.java254
-rw-r--r--bash_completion/MODULE_LICENSE_APACHE20
-rw-r--r--bash_completion/NOTICE189
-rw-r--r--bash_completion/README13
-rw-r--r--bash_completion/adb.bash382
-rw-r--r--ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java1
-rw-r--r--ddms/libs/ddmlib/src/com/android/ddmlib/HandleNativeHeap.java53
-rw-r--r--ddms/libs/ddmlib/src/com/android/ddmlib/NativeAllocationInfo.java10
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/Addr2Line.java10
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java84
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java14
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java190
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java119
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java28
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java43
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java100
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java78
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java22
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java69
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-completion21.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion11.txt2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion12.txt2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion6.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-applyCompletion42.diff4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-applyCompletion43.diff4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-completion65.txt4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10.xml10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion9-expected-completion64.txt2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion44.diff4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion66.txt7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion67.txt7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion68.txt3
-rw-r--r--files/ant/main_rules.xml23
-rw-r--r--files/devices.xml28
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java11
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java5
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java104
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java88
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java30
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java52
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java30
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java26
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java55
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java54
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java16
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java26
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java44
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockExtraPackage.java64
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java33
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformToolPackage.java12
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockToolPackage.java12
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java399
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java1180
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java170
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java164
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesDiffLogicTest.java789
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java173
74 files changed, 4504 insertions, 1162 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecLoopTask.java b/anttasks/src/com/android/ant/AaptExecLoopTask.java
index d7d53c9..6c98cbd 100644
--- a/anttasks/src/com/android/ant/AaptExecLoopTask.java
+++ b/anttasks/src/com/android/ant/AaptExecLoopTask.java
@@ -18,7 +18,6 @@ package com.android.ant;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.ExecTask;
import org.apache.tools.ant.types.Path;
@@ -51,7 +50,7 @@ import java.util.ArrayList;
* <tr><td></td><td></td><td></td></tr>
* </table>
*/
-public final class AaptExecLoopTask extends Task {
+public final class AaptExecLoopTask extends BaseTask {
/**
* Class representing a &lt;nocompress&gt; node in the main task XML.
@@ -77,6 +76,7 @@ public final class AaptExecLoopTask extends Task {
private boolean mForce = true; // true due to legacy reasons
private boolean mDebug = false;
private boolean mVerbose = false;
+ private boolean mUseCrunchCache = false;
private int mVersionCode = 0;
private String mManifest;
private ArrayList<Path> mResources;
@@ -120,6 +120,14 @@ public final class AaptExecLoopTask extends Task {
mVerbose = verbose;
}
+ /**
+ * Sets the value of the "usecrunchcache" attribute
+ * @param usecrunch whether to use the crunch cache.
+ */
+ public void setNoCrunch(boolean nocrunch) {
+ mUseCrunchCache = nocrunch;
+ }
+
public void setVersioncode(String versionCode) {
if (versionCode.length() > 0) {
try {
@@ -253,48 +261,87 @@ public final class AaptExecLoopTask extends Task {
public void execute() throws BuildException {
Project taskProject = getProject();
- // first do a full resource package
- callAapt(null /*customPackage*/);
+ String libPkgProp = null;
// if the parameters indicate generation of the R class, check if
// more R classes need to be created for libraries.
if (mRFolder != null && new File(mRFolder).isDirectory()) {
- String libPkgProp = taskProject.getProperty(AntConstants.PROP_PROJECT_LIBS_PKG);
+ libPkgProp = taskProject.getProperty(AntConstants.PROP_PROJECT_LIBS_PKG);
if (libPkgProp != null) {
- // get the main package to compare in case the libraries use the same
- String mainPackage = taskProject.getProperty(AntConstants.PROP_MANIFEST_PACKAGE);
-
- String[] libPkgs = libPkgProp.split(";");
- for (String libPkg : libPkgs) {
- if (libPkg.length() > 0 && mainPackage.equals(libPkg) == false) {
- // FIXME: instead of recreating R.java from scratch, maybe copy
- // the files (R.java and manifest.java)? This would force to replace
- // the package line on the fly.
- callAapt(libPkg);
- }
- }
+ // Replace ";" with ":" since that's what aapt expects
+ libPkgProp = libPkgProp.replace(';', ':');
}
}
+ // Call aapt. If there are libraries, we'll pass a non-null string of libs.
+ callAapt(libPkgProp);
}
/**
* Calls aapt with the given parameters.
* @param resourceFilter the resource configuration filter to pass to aapt (if configName is
* non null)
- * @param customPackage an optional custom package.
+ * @param extraPackages an optional list of colon-separated packages. Can be null
+ * Ex: com.foo.one:com.foo.two:com.foo.lib
*/
- private void callAapt(String customPackage) {
+ private void callAapt(String extraPackages) {
Project taskProject = getProject();
final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory();
+ // Get whether we have libraries
+ Object libResRef = taskProject.getReference(AntConstants.PROP_PROJECT_LIBS_RES_REF);
+
+ // Set up our folders to check for changed files
+ ArrayList<File> watchPaths = new ArrayList<File>();
+ // We need to watch for changes in the main project res folder
+ for (Path pathList : mResources) {
+ for (String path : pathList.list()) {
+ watchPaths.add(new File(path));
+ }
+ }
+ // and if libraries exist, in their res folders
+ if (libResRef instanceof Path) {
+ for (String path : ((Path)libResRef).list()) {
+ watchPaths.add(new File(path));
+ }
+ }
+ // If we're here to generate a .ap_ file we need to watch assets as well
+ if (!generateRClass) {
+ File assetsDir = new File(mAssets);
+ if (mAssets != null && assetsDir.isDirectory()) {
+ watchPaths.add(assetsDir);
+ }
+ }
+
+ // Now we figure out what we need to do
if (generateRClass) {
- } else if (mResourceFilter == null) {
- System.out.println("Creating full resource package...");
+ // Check to see if our dependencies have changed. If not, then skip
+ if (initDependencies(mRFolder + File.separator + "R.d", watchPaths)
+ && dependenciesHaveChanged() == false) {
+ System.out.println("No changed resources. R.java and Manifest.java untouched.");
+ return;
+ }
} else {
- System.out.println(String.format(
- "Creating resource package with filter: (%1$s)...",
- mResourceFilter));
+ // Find our dependency file. It should have the same name as our target .ap_ but
+ // with a .d extension
+ String dependencyFilePath = mApkFolder + File.separator + mApkName;
+ dependencyFilePath =
+ dependencyFilePath.substring(0, dependencyFilePath.lastIndexOf(".")) + ".d";
+
+ // Check to see if our dependencies have changed
+ if (initDependencies(dependencyFilePath , watchPaths)
+ && dependenciesHaveChanged() == false) {
+ System.out.println("No changed resources or assets. " + dependencyFilePath
+ + " remains untouched");
+ return;
+ }
+ if (mResourceFilter == null) {
+ System.out.println("Creating full resource package...");
+ } else {
+ System.out.println(String.format(
+ "Creating resource package with filter: (%1$s)...",
+ mResourceFilter));
+ }
}
// create a task for the default apk.
@@ -308,6 +355,11 @@ public final class AaptExecLoopTask extends Task {
// aapt command. Only "package" is supported at this time really.
task.createArg().setValue(mCommand);
+ // No crunch flag
+ if (mUseCrunchCache) {
+ task.createArg().setValue("--no-crunch");
+ }
+
// force flag
if (mForce) {
task.createArg().setValue("-f");
@@ -351,13 +403,12 @@ public final class AaptExecLoopTask extends Task {
}
}
- if (customPackage != null) {
- task.createArg().setValue("--custom-package");
- task.createArg().setValue(customPackage);
+ if (extraPackages != null) {
+ task.createArg().setValue("--extra-packages");
+ task.createArg().setValue(extraPackages);
}
// if the project contains libraries, force auto-add-overlay
- Object libResRef = taskProject.getReference(AntConstants.PROP_PROJECT_LIBS_RES_REF);
if (libResRef != null) {
task.createArg().setValue("--auto-add-overlay");
}
@@ -429,6 +480,9 @@ public final class AaptExecLoopTask extends Task {
task.createArg().setValue(mRFolder);
}
+ // Use dependency generation
+ task.createArg().setValue("--generate-dependencies");
+
// final setup of the task
task.setProject(taskProject);
task.setOwningTarget(getOwningTarget());
diff --git a/anttasks/src/com/android/ant/BaseTask.java b/anttasks/src/com/android/ant/BaseTask.java
new file mode 100644
index 0000000..2126d3f
--- /dev/null
+++ b/anttasks/src/com/android/ant/BaseTask.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * A base class for the ant task that contains logic for handling dependency files
+ */
+public class BaseTask extends Task {
+
+ private DependencyGraph mDependencies;
+
+ /*
+ * (non-Javadoc)
+ *
+ * Executes the loop. Based on the values inside default.properties, this will
+ * create alternate temporary ap_ files.
+ *
+ * @see org.apache.tools.ant.Task#execute()
+ */
+ @Override
+ public void execute() throws BuildException {
+
+ }
+
+ /**
+ * Set up the dependency graph by passing it the location of the ".d" file
+ * @param dependencyFile path to the dependency file to use
+ * @param watchPaths a list of folders to watch for new files
+ * @return true if the dependency graph was sucessfully initialized
+ */
+ protected boolean initDependencies(String dependencyFile, ArrayList<File> watchPaths) {
+ File depFile = new File(dependencyFile);
+ if (depFile.exists()) {
+ mDependencies = new DependencyGraph(dependencyFile, watchPaths);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Wrapper check to see if we need to execute this task at all
+ * @return true if the DependencyGraph reports that our prereqs or targets
+ * have changed since the last run
+ */
+ protected boolean dependenciesHaveChanged() {
+ assert mDependencies != null : "Dependencies have not been initialized";
+ return mDependencies.dependenciesHaveChanged();
+ }
+}
diff --git a/anttasks/src/com/android/ant/DependencyGraph.java b/anttasks/src/com/android/ant/DependencyGraph.java
new file mode 100644
index 0000000..5939a92
--- /dev/null
+++ b/anttasks/src/com/android/ant/DependencyGraph.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2011 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.ant;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class takes care of dependency tracking for all targets and prerequisites listed in
+ * a single dependency file. A dependency graph always has a dependency file associated with it
+ * for the duration of its lifetime
+ */
+public class DependencyGraph {
+
+ // Files that we know about from the dependency file
+ private Set<File> mTargets = Collections.emptySet();
+ private Set<File> mPrereqs = mTargets;
+ private ArrayList<File> mWatchPaths;
+
+ public DependencyGraph(String dependencyFilePath, ArrayList<File> watchPaths) {
+ mWatchPaths = watchPaths;
+ parseDependencyFile(dependencyFilePath);
+ }
+
+ /**
+ * Check all the dependencies to see if anything has changed.
+ * @return true if new prerequisites have appeared, target files are missing or if
+ * prerequisite files have been modified since the last target generation.
+ */
+ public boolean dependenciesHaveChanged() {
+ boolean noFile = (mTargets.size() == 0);
+ boolean missingPrereq = missingPrereqFile();
+ boolean newPrereq = newPrereqFile();
+ boolean missingTarget = missingTargetFile();
+ boolean modPrereq = modifiedPrereq();
+
+ if (noFile) {
+ System.out.println("No Dependency File Found");
+ }
+ if (missingPrereq) {
+ System.out.println("Found Deleted Prereq File");
+ }
+ if (newPrereq) {
+ System.out.println("Found New Prereq File");
+ }
+ if (missingTarget) {
+ System.out.println("Found Deleted Target File");
+ }
+ if (modPrereq) {
+ System.out.println("Found Modified Prereq File");
+ }
+ // If no dependency file has been set up, then we'll just return true
+ // if we have a dependency file, we'll check to see what's been changed
+ return noFile || missingPrereq || newPrereq || missingTarget || modPrereq;
+ }
+
+ /**
+ * Parses the given dependency file and stores the file paths
+ *
+ * @param dependencyFilePath the dependency file
+ */
+ private void parseDependencyFile(String dependencyFilePath) {
+ // Read in our dependency file
+ String content = readFile(dependencyFilePath);
+ if (content == null) {
+ System.err.println("ERROR: Couldn't read " + dependencyFilePath);
+ return;
+ }
+
+ // The format is something like:
+ // output1 output2 [...]: dep1 dep2 [...]
+ // expect it's likely split on several lines. So let's move it back on a single line
+ // first
+ String[] lines = content.toString().split("\n");
+ StringBuilder sb = new StringBuilder(content.length());
+ for (String line : lines) {
+ line = line.trim();
+ if (line.endsWith("\\")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ sb.append(line);
+ }
+
+ // split the left and right part
+ String[] files = sb.toString().split(":");
+
+ // get the target files:
+ String[] targets = files[0].trim().split(" ");
+
+ String[] prereqs = {};
+ // Check to make sure our dependency file is okay
+ if (files.length < 1) {
+ System.err.println(
+ "Warning! Dependency file does not list any prerequisites after ':' ");
+ } else {
+ // and the prerequisite files:
+ prereqs = files[1].trim().split(" ");
+ }
+
+ mTargets = new HashSet<File>(targets.length);
+ for (String path : targets) {
+ mTargets.add(new File(path));
+ }
+ mPrereqs = new HashSet<File>(prereqs.length);
+ for (String path : prereqs) {
+ mPrereqs.add(new File(path));
+ }
+ }
+
+ /**
+ * Check all the folders we know about to see if there have been new
+ * files added to them.
+ * @return true if a new file is encountered in the dependency folders
+ */
+ private boolean newPrereqFile() {
+ for (File dir : mWatchPaths) {
+ if (newFileInTree(dir)) {
+ return true;
+ }
+ }
+ // If we make it all the way through our directories we're good.
+ return false;
+ }
+
+ /**
+ * Check all the files in the tree under root and check to see if the files are
+ * listed under the dependencies. Recurses into subdirs.
+ * @param root the root of the file tree to search through
+ * @return true if a file is encountered in the tree that is not in our list of prereqs
+ */
+ private boolean newFileInTree(File root) {
+ File[] files = root.listFiles();
+ if (files == null) {
+ System.err.println("ERROR " + root.toString() + " is not a dir or can't be read");
+ return false;
+ }
+ // Loop through files in this folder
+ for (File file : files) {
+ // If this is a directory, recurse into it
+ if (file.isDirectory()) {
+ if (newFileInTree(file)) {
+ return true;
+ }
+ } else if (file.isFile() && mPrereqs.contains(file) == false) {
+ return true;
+ }
+ }
+ // If we got to here then we didn't find anything interesting
+ return false;
+ }
+
+ /**
+ * Check all the prereq files we know about to make sure they're still there
+ * @return true if any of the prereq files are missing.
+ */
+ private boolean missingPrereqFile() {
+ // Loop through our prereq files and make sure they still exist
+ for (File prereq : mPrereqs) {
+ if (prereq.exists() == false) {
+ return true;
+ }
+ }
+ // If we get this far, then all our targets are okay
+ return false;
+ }
+
+ /**
+ * Check all the target files we know about to make sure they're still there
+ * @return true if any of the target files are missing.
+ */
+ private boolean missingTargetFile() {
+ // Loop through our target files and make sure they still exist
+ for (File target : mTargets) {
+ if (target.exists() == false) {
+ return true;
+ }
+ }
+ // If we get this far, then all our targets are okay
+ return false;
+ }
+
+ /**
+ * Check to see if any of the prerequisite files have been modified since
+ * the targets were last updated.
+ * @return true if the latest prerequisite modification is after the oldest
+ * target modification.
+ */
+ private boolean modifiedPrereq() {
+ // Find the oldest target
+ long oldestTarget = Long.MAX_VALUE;
+ for (File target : mTargets) {
+ if (target.lastModified() < oldestTarget) {
+ oldestTarget = target.lastModified();
+ }
+ }
+
+ // Find the newest prerequisite
+ long newestPrereq = 0;
+ for (File prereq : mPrereqs) {
+ if (prereq.lastModified() > newestPrereq) {
+ newestPrereq = prereq.lastModified();
+ }
+ }
+
+ // And return the comparison
+ return newestPrereq > oldestTarget;
+ }
+
+ /**
+ * Reads and returns the content of a text file.
+ * @param filepath the file path to the text file
+ * @return null if the file could not be read
+ */
+ private static String readFile(String filepath) {
+ try {
+ FileInputStream fStream = new FileInputStream(filepath);
+ if (fStream != null) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(fStream));
+
+ String line;
+ StringBuilder total = new StringBuilder(reader.readLine());
+ while ((line = reader.readLine()) != null) {
+ total.append('\n');
+ total.append(line);
+ }
+ return total.toString();
+ }
+ } catch (IOException e) {
+ // we'll just return null
+ }
+ return null;
+ }
+}
diff --git a/bash_completion/MODULE_LICENSE_APACHE2 b/bash_completion/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bash_completion/MODULE_LICENSE_APACHE2
diff --git a/bash_completion/NOTICE b/bash_completion/NOTICE
new file mode 100644
index 0000000..20da40d
--- /dev/null
+++ b/bash_completion/NOTICE
@@ -0,0 +1,189 @@
+
+ Copyright (c) 2011, 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/bash_completion/README b/bash_completion/README
new file mode 100644
index 0000000..55461cf
--- /dev/null
+++ b/bash_completion/README
@@ -0,0 +1,13 @@
+This directory contains scripts that are intended to be used with
+Bourne Again SHell (bash)'s programmable completion.
+
+See http://www.gnu.org/s/bash/manual/bash.html#Programmable-Completion for
+more information on programmable completion in bash.
+
+To use the scripts, simply source them into your environment. Example:
+
+ source sdk/bash_completion/adb
+
+or:
+
+ . sdk/bash_completion/adb
diff --git a/bash_completion/adb.bash b/bash_completion/adb.bash
new file mode 100644
index 0000000..12b36ef
--- /dev/null
+++ b/bash_completion/adb.bash
@@ -0,0 +1,382 @@
+# /* vim: set ai ts=4 ft=sh: */
+#
+# Copyright 2011, 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.
+#
+
+_adb() {
+ unset -v have
+ type $1 &> /dev/null && have="yes"
+
+ if [ "$have" != "yes" ]; then
+ return
+ fi
+
+ local where i cur serial
+ COMPREPLY=()
+
+ serial="${ANDROID_SERIAL:-none}"
+ where=OPTIONS
+ for ((i=1; i <= COMP_CWORD; i++)); do
+ cur="${COMP_WORDS[i]}"
+ case "${cur}" in
+ -s)
+ where=OPT_SERIAL
+ ;;
+ -p)
+ where=OPT_PATH
+ ;;
+ -*)
+ where=OPTIONS
+ ;;
+ *)
+ if [[ $where == OPT_SERIAL ]]; then
+ where=OPT_SERIAL_ARG
+ elif [[ $where == OPT_SERIAL_ARG ]]; then
+ serial=${cur}
+ where=OPTIONS
+ else
+ where=COMMAND
+ break
+ fi
+ ;;
+ esac
+ done
+
+ if [[ $where == COMMAND && $i -ge $COMP_CWORD ]]; then
+ where=OPTIONS
+ fi
+
+ OPTIONS="-d -e -s -p"
+ COMMAND="devices connect disconnect push pull sync shell emu logcat lolcat forward jdwp install uninstall bugreport help version start-server kill-server get-state get-serialno status-window remount reboot reboot-bootloader root usb tcpip"
+
+ case $where in
+ OPTIONS|OPT_SERIAL|OPT_PATH)
+ COMPREPLY=( $(compgen -W "$OPTIONS $COMMAND" -- "$cur") )
+ ;;
+ OPT_SERIAL_ARG)
+ local devices=$(command adb devices '2>' /dev/null | grep -v "List of devices" | awk '{ print $1 }')
+ COMPREPLY=( $(compgen -W "${devices}" -- ${cur}) )
+ ;;
+ COMMAND)
+ if [[ $i -eq $COMP_CWORD ]]; then
+ COMPREPLY=( $(compgen -W "$COMMAND" -- "$cur") )
+ else
+ i=$((i+1))
+ case "${cur}" in
+ install)
+ _adb_cmd_install "$serial" $i
+ ;;
+ pull)
+ _adb_cmd_pull "$serial" $i
+ ;;
+ push)
+ _adb_cmd_push "$serial" $i
+ ;;
+ reboot)
+ if [[ $COMP_CWORD == $i ]]; then
+ args="bootloader recovery"
+ COMPREPLY=( $(compgen -W "${args}" -- "${COMP_WORDS[i]}") )
+ fi
+ ;;
+ shell)
+ _adb_cmd_shell "$serial" $i
+ ;;
+ uninstall)
+ _adb_cmd_uninstall "$serial" $i
+ ;;
+ esac
+ fi
+ ;;
+ esac
+
+ return 0
+}
+
+_adb_cmd_install() {
+ local serial i cur where
+
+ serial=$1
+ i=$2
+
+ where=OPTIONS
+ for ((; i <= COMP_CWORD; i++)); do
+ cur="${COMP_WORDS[i]}"
+ case "${cur}" in
+ -*)
+ where=OPTIONS
+ ;;
+ *)
+ where=FILE
+ break
+ ;;
+ esac
+ done
+
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ if [[ $where == OPTIONS ]]; then
+ COMPREPLY=( $(compgen -W "-l -r -s" -- "${cur}") )
+ return
+ fi
+
+ _adb_util_complete_local_file "${cur}" '!*.apk'
+}
+
+_adb_cmd_push() {
+ local serial IFS=$'\n' i cur
+
+ serial=$1
+ i=$2
+
+ cur="${COMP_WORDS[COMP_CWORD]}"
+
+ if [[ $COMP_CWORD == $i ]]; then
+ _adb_util_complete_local_file "${cur}"
+ elif [[ $COMP_CWORD == $(($i+1)) ]]; then
+ if [ "${cur}" == "" ]; then
+ cur="/"
+ fi
+ _adb_util_list_files $serial "${cur}"
+ fi
+}
+
+_adb_cmd_pull() {
+ local serial IFS=$'\n' i cur
+
+ serial=$1
+ i=$2
+
+ cur="${COMP_WORDS[COMP_CWORD]}"
+
+ if [[ $COMP_CWORD == $i ]]; then
+ if [ "${cur}" == "" ]; then
+ cur="/"
+ fi
+ _adb_util_list_files $serial "${cur}"
+ elif [[ $COMP_CWORD == $(($i+1)) ]]; then
+ _adb_util_complete_local_file "${cur}"
+ fi
+}
+
+_adb_cmd_shell() {
+ local serial IFS=$'\n' i cur
+ local -a args
+
+ serial=$1
+ i=$2
+
+ cur="${COMP_WORDS[i]}"
+ if [ "$serial" != "none" ]; then
+ args=(-s $serial)
+ fi
+
+ if [[ $i -eq $COMP_CWORD && ${cur:0:1} != "/" ]]; then
+ paths=$(command adb ${args[@]} shell echo '$'PATH 2> /dev/null | tr -d '\r' | tr : '\n')
+ COMMAND=$(command adb ${args[@]} shell ls $paths '2>' /dev/null | tr -d '\r' | {
+ while read -r tmp; do
+ command=${tmp##*/}
+ printf '%s\n' "$command"
+ done
+ })
+ COMPREPLY=( $(compgen -W "$COMMAND" -- "$cur") )
+ return 0
+ fi
+
+ i=$((i+1))
+ case "$cur" in
+ ls)
+ _adb_shell_ls $serial $i
+ ;;
+ /*)
+ _adb_util_list_files $serial "$cur"
+ ;;
+ *)
+ COMPREPLY=( )
+ ;;
+ esac
+
+ return 0
+}
+
+_adb_cmd_uninstall() {
+ local serial i where cur packages
+
+ serial=$1
+ i=$2
+ if [ "$serial" != "none" ]; then
+ args=(-s $serial)
+ fi
+
+ where=OPTIONS
+ for ((; i <= COMP_CWORD; i++)); do
+ cur="${COMP_WORDS[i]}"
+ case "${cur}" in
+ -*)
+ where=OPTIONS
+ ;;
+ *)
+ where=FILE
+ break
+ ;;
+ esac
+ done
+
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ if [[ $where == OPTIONS ]]; then
+ COMPREPLY=( $(compgen -W "-k" -- "${cur}") )
+ fi
+
+ packages="$(
+ command adb ${args[@]} shell pm list packages '2>' /dev/null 2> /dev/null | tr -d '\r' | {
+ while read -r tmp; do
+ local package=${tmp#package:}
+ echo -n "${package} "
+ done
+ }
+ )"
+
+ COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "${packages}" -- "${cur}") )
+}
+
+_adb_shell_ls() {
+ local serial i cur file
+ local -a args
+
+ serial=$1
+ i=$2
+ if [ "$serial" != "none" ]; then
+ args=(-s $serial)
+ fi
+
+ where=OPTIONS
+ for ((; i <= COMP_CWORD; i++)); do
+ cur="${COMP_WORDS[i]}"
+ case "${cur}" in
+ -*)
+ where=OPTIONS
+ ;;
+ *)
+ where=FILE
+ break
+ ;;
+ esac
+ done
+
+ file="${COMP_WORDS[COMP_CWORD]}"
+ if [[ ${file} == "" ]]; then
+ file="/"
+ fi
+
+ case $where in
+ OPTIONS)
+ COMPREPLY=( $(compgen -W "$OPTIONS" -- "$cur") )
+ _adb_util_list_files $serial "$file"
+ ;;
+ FILE)
+ _adb_util_list_files $serial "$file"
+ ;;
+ esac
+
+ return 0
+}
+
+_adb_util_list_files() {
+ local serial dir IFS=$'\n'
+ local -a toks
+ local -a args
+
+ serial="$1"
+ file="$2"
+
+ if [ "$serial" != "none" ]; then
+ args=(-s $serial)
+ fi
+
+ toks=( ${toks[@]-} $(
+ command adb ${args[@]} shell ls -dF ${file}"*" '2>' /dev/null 2> /dev/null | tr -d '\r' | {
+ while read -r tmp; do
+ filetype=${tmp%% *}
+ filename=${tmp:${#filetype}+1}
+ if [[ ${filetype:${#filetype}-1:1} == d ]]; then
+ printf '%s/\n' "$filename"
+ else
+ printf '%s\n' "$filename"
+ fi
+ done
+ }
+ ))
+
+ # Since we're probably doing file completion here, don't add a space after.
+ if [[ $(type -t compopt) = "builtin" ]]; then
+ compopt -o nospace
+ fi
+
+ COMPREPLY=( ${COMPREPLY[@]:-} "${toks[@]}" )
+}
+
+_adb_util_complete_local_file()
+{
+ local file xspec i j
+ local -a dirs files
+
+ file=$1
+ xspec=$2
+
+ # Since we're probably doing file completion here, don't add a space after.
+ if [[ $(type -t compopt) = "builtin" ]]; then
+ compopt -o plusdirs
+ if [[ "${xspec}" == "" ]]; then
+ COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -f -- "${cur}") )
+ else
+ compopt +o filenames
+ COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -f -X "${xspec}" -- "${cur}") )
+ fi
+ else
+ # Work-around for shells with no compopt
+
+ dirs=( $(compgen -d -- "${cur}" ) )
+
+ if [[ "${xspec}" == "" ]]; then
+ files=( ${COMPREPLY[@]:-} $(compgen -f -- "${cur}") )
+ else
+ files=( ${COMPREPLY[@]:-} $(compgen -f -X "${xspec}" -- "${cur}") )
+ fi
+
+ COMPREPLY=( $(
+ for i in "${files[@]}"; do
+ local skip=
+ for j in "${dirs[@]}"; do
+ if [[ $i == $j ]]; then
+ skip=1
+ break
+ fi
+ done
+ [[ -n $skip ]] || printf "%s\n" "$i"
+ done
+ ))
+
+ COMPREPLY=( ${COMPREPLY[@]:-} $(
+ for i in "${dirs[@]}"; do
+ printf "%s/\n" "$i"
+ done
+ ))
+ fi
+}
+
+
+if [[ $(type -t compopt) = "builtin" ]]; then
+ complete -F _adb adb
+else
+ complete -o nospace -F _adb adb
+fi
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java b/ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java
index f697cba..253f41f 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java
@@ -195,6 +195,7 @@ public final class AndroidDebugBridge {
HandleHeap.register(monitorThread);
HandleWait.register(monitorThread);
HandleProfiling.register(monitorThread);
+ HandleNativeHeap.register(monitorThread);
}
/**
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/HandleNativeHeap.java b/ddms/libs/ddmlib/src/com/android/ddmlib/HandleNativeHeap.java
index 2b91b1e..8482863 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/HandleNativeHeap.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/HandleNativeHeap.java
@@ -171,7 +171,7 @@ final class HandleNativeHeap extends ChunkHandler {
buffer.getInt() /* allocations */);
for (int j = 0 ; j < backtraceSize ; j++) {
- long addr = ((long)buffer.getInt()) & 0x00000000ffffffffL;
+ long addr = (buffer.getInt()) & 0x00000000ffffffffL;
info.addStackCallAddress(addr);;
}
@@ -203,8 +203,8 @@ final class HandleNativeHeap extends ChunkHandler {
buffer.order(ByteOrder.BIG_ENDIAN);
int id = buffer.getInt();
- int unitsize = (int) buffer.get();
- long startAddress = (long) buffer.getInt() & 0x00000000ffffffffL;
+ int unitsize = buffer.get();
+ long startAddress = buffer.getInt() & 0x00000000ffffffffL;
int offset = buffer.getInt();
int allocationUnitCount = buffer.getInt();
@@ -218,8 +218,8 @@ final class HandleNativeHeap extends ChunkHandler {
// read the usage
while (buffer.position() < buffer.limit()) {
- int eState = (int)buffer.get() & 0x000000ff;
- int eLen = ((int)buffer.get() & 0x000000ff) + 1;
+ int eState = buffer.get() & 0x000000ff;
+ int eLen = (buffer.get() & 0x000000ff) + 1;
//Log.e("ddm-nativeheap", "solidity: " + (eState & 0x7) + " - kind: "
// + ((eState >> 3) & 0x7) + " - len: " + eLen);
}
@@ -254,36 +254,29 @@ final class HandleNativeHeap extends ChunkHandler {
long tmpStart = Long.parseLong(line.substring(0, 8), 16);
long tmpEnd = Long.parseLong(line.substring(9, 17), 16);
- /*
- * only check for library addresses as defined in
- * //device/config/prelink-linux-arm.map
- */
- if (tmpStart >= 0x0000000080000000L && tmpStart <= 0x00000000BFFFFFFFL) {
+ int index = line.indexOf('/');
- int index = line.indexOf('/');
+ if (index == -1)
+ continue;
- if (index == -1)
- continue;
+ String tmpLib = line.substring(index);
- String tmpLib = line.substring(index);
+ if (library == null ||
+ (library != null && tmpLib.equals(library) == false)) {
- if (library == null ||
- (library != null && tmpLib.equals(library) == false)) {
-
- if (library != null) {
- cd.addNativeLibraryMapInfo(startAddr, endAddr, library);
- Log.d("ddms", library + "(" + Long.toHexString(startAddr) +
- " - " + Long.toHexString(endAddr) + ")");
- }
-
- // now init the new library
- library = tmpLib;
- startAddr = tmpStart;
- endAddr = tmpEnd;
- } else {
- // add the new end
- endAddr = tmpEnd;
+ if (library != null) {
+ cd.addNativeLibraryMapInfo(startAddr, endAddr, library);
+ Log.d("ddms", library + "(" + Long.toHexString(startAddr) +
+ " - " + Long.toHexString(endAddr) + ")");
}
+
+ // now init the new library
+ library = tmpLib;
+ startAddr = tmpStart;
+ endAddr = tmpEnd;
+ } else {
+ // add the new end
+ endAddr = tmpEnd;
}
} catch (NumberFormatException e) {
e.printStackTrace();
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/NativeAllocationInfo.java b/ddms/libs/ddmlib/src/com/android/ddmlib/NativeAllocationInfo.java
index 41d63b2..9909b9a 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/NativeAllocationInfo.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/NativeAllocationInfo.java
@@ -51,6 +51,8 @@ public final class NativeAllocationInfo {
sAllocFunctionFilter.add("chk_memalign"); //$NON-NLS-1$
sAllocFunctionFilter.add("Malloc"); //$NON-NLS-1$
sAllocFunctionFilter.add("leak_memalign"); //$NON-NLS-1$
+ sAllocFunctionFilter.add("strcmp"); //$NON-NLS-1$
+ sAllocFunctionFilter.add("dlrealloc"); //$NON-NLS-1$
}
private final int mSize;
@@ -247,7 +249,7 @@ public final class NativeAllocationInfo {
long addr = addrIterator.next();
NativeStackCallInfo info = sourceIterator.next();
if (addr != 0 && info != null) {
- if (isRelevant(info.getMethodName())) {
+ if (isRelevant(info.getMethodName(), addr)) {
return info;
}
}
@@ -265,14 +267,16 @@ public final class NativeAllocationInfo {
/**
* Returns true if the method name is relevant.
* @param methodName the method name to test.
+ * @param addr the original address. This is used because sometimes the name of the method is
+ * the address itself which is not relevant
*/
- private boolean isRelevant(String methodName) {
+ private boolean isRelevant(String methodName, long addr) {
for (String filter : sAllocFunctionFilter) {
if (methodName.contains(filter)) {
return false;
}
}
- return true;
+ return methodName.equals(Long.toString(addr, 16)) == false;
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/Addr2Line.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/Addr2Line.java
index 5663fc2..c921ac2 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/Addr2Line.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/Addr2Line.java
@@ -16,7 +16,8 @@
package com.android.ddmuilib;
-import com.android.ddmlib.*;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.NativeStackCallInfo;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
@@ -138,9 +139,14 @@ public class Addr2Line {
symbols = DdmUiPreferences.getSymbolDirectory();
}
+ String addr2Line = System.getenv("ANDROID_ADDR2LINE");
+ if (addr2Line == null) {
+ addr2Line = DdmUiPreferences.getAddr2Line();
+ }
+
// build the command line
String[] command = new String[5];
- command[0] = DdmUiPreferences.getAddr2Line();
+ command[0] = addr2Line;
command[1] = "-C";
command[2] = "-f";
command[3] = "-e";
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java
index 0b2460b..c7f7b4f 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java
@@ -77,7 +77,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
private static final int NUM_PALETTE_ENTRIES = HeapSegmentElement.KIND_NATIVE+2 +1;
private static final String[] mMapLegend = new String[NUM_PALETTE_ENTRIES];
private static final PaletteData mMapPalette = createPalette();
-
+
private static final int ALLOC_DISPLAY_ALL = 0;
private static final int ALLOC_DISPLAY_PRE_ZYGOTE = 1;
private static final int ALLOC_DISPLAY_POST_ZYGOTE = 2;
@@ -137,7 +137,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
private Table mDetailTable;
private Label mImage;
-
+
private int mAllocDisplayMode = ALLOC_DISPLAY_ALL;
/**
@@ -163,7 +163,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
/** list of NativeAllocationInfo objects filled with the list from ClientData */
private final ArrayList<NativeAllocationInfo> mAllocations =
new ArrayList<NativeAllocationInfo>();
-
+
/** list of the {@link NativeAllocationInfo} being displayed based on the selection
* of {@link #mAllocDisplayCombo}.
*/
@@ -271,9 +271,9 @@ public final class NativeHeapPanel extends BaseHeapPanel {
if (info.isStackCallResolved() == false) {
final Long[] list = info.getStackCallAddresses();
final int size = list.length;
-
+
ArrayList<NativeStackCallInfo> resolvedStackCall =
- new ArrayList<NativeStackCallInfo>();
+ new ArrayList<NativeStackCallInfo>();
for (int i = 0; i < size; i++) {
long addr = list[i];
@@ -289,7 +289,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
resolvedStackCall.add(source);
}
-
+
info.setResolvedStackCall(resolvedStackCall);
}
// after every DISPLAY_PER_PAGE we ask for a ui refresh, unless
@@ -488,7 +488,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
gl.verticalSpacing = 0;
mBase.setLayout(gl);
mBase.setLayoutData(new GridData(GridData.FILL_BOTH));
-
+
// composite for <update btn> <status>
Composite tmp = new Composite(mBase, SWT.NONE);
tmp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
@@ -527,9 +527,9 @@ public final class NativeHeapPanel extends BaseHeapPanel {
Composite top_layout = new Composite(mBase, SWT.NONE);
top_layout.setLayout(gl = new GridLayout(4, false));
gl.marginWidth = gl.marginHeight = 0;
-
+
new Label(top_layout, SWT.NONE).setText("Show:");
-
+
mAllocDisplayCombo = new Combo(top_layout, SWT.DROP_DOWN | SWT.READ_ONLY);
mAllocDisplayCombo.setLayoutData(new GridData(
GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
@@ -543,7 +543,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
}
});
mAllocDisplayCombo.select(0);
-
+
// separator
Label separator = new Label(top_layout, SWT.SEPARATOR | SWT.VERTICAL);
GridData gd;
@@ -569,7 +569,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
}
}
});
-
+
/*
* TODO: either fix the diff mechanism or remove it altogether.
mDiffUpdateButton = new Button(top_layout, SWT.NONE);
@@ -619,7 +619,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
}
});
mDisplayModeCombo.setEnabled(false);
-
+
mSymbolsButton = new Button(top_layout, SWT.PUSH);
mSymbolsButton.setText("Load Symbols");
mSymbolsButton.setEnabled(false);
@@ -651,7 +651,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
return mBase;
}
-
+
/**
* Sets the focus to the proper control inside the panel.
*/
@@ -754,7 +754,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
// we're done: do something
mDisplayModeCombo.setEnabled(true);
mSaveButton.setEnabled(true);
-
+
mStackCallThread = null;
} else {
// work in progress, update the progress bar.
@@ -773,7 +773,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
// get the current selection of the allocation
int index = mAllocationTable.getSelectionIndex();
NativeAllocationInfo info = null;
-
+
if (index != -1) {
info = (NativeAllocationInfo)mAllocationTable.getItem(index).getData();
}
@@ -811,13 +811,13 @@ public final class NativeHeapPanel extends BaseHeapPanel {
addTableToFocusListener(mLibraryAllocationTable);
addTableToFocusListener(mDetailTable);
}
-
+
protected void onAllocDisplayChange() {
mAllocDisplayMode = mAllocDisplayCombo.getSelectionIndex();
-
+
// create the new list
updateAllocDisplayList();
-
+
updateTotalMemoryDisplay();
// reset the ui.
@@ -825,7 +825,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
updatePageUI();
switchDisplayMode();
}
-
+
private void updateAllocDisplayList() {
mTotalSize = 0;
mDisplayedAllocations.clear();
@@ -839,9 +839,9 @@ public final class NativeHeapPanel extends BaseHeapPanel {
continue;
}
}
-
+
int count = mDisplayedAllocations.size();
-
+
mPageCount = count / DISPLAY_PER_PAGE;
// need to add a page for the rest of the div
@@ -849,7 +849,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
mPageCount++;
}
}
-
+
private void updateTotalMemoryDisplay() {
switch (mAllocDisplayMode) {
case ALLOC_DISPLAY_ALL:
@@ -892,9 +892,13 @@ public final class NativeHeapPanel extends BaseHeapPanel {
}
private void initAllocationDisplay() {
+ if (mStackCallThread != null) {
+ mStackCallThread.quit();
+ }
+
mAllocations.clear();
mAllocations.addAll(mClientData.getNativeAllocationList());
-
+
updateAllocDisplayList();
// if we have a previous clientdata and it matches the current one. we
@@ -1116,12 +1120,12 @@ public final class NativeHeapPanel extends BaseHeapPanel {
private void fillDetailTable(final NativeAllocationInfo mi) {
mDetailTable.removeAll();
mDetailTable.setRedraw(false);
-
+
try {
// populate the detail Table with the back trace
Long[] addresses = mi.getStackCallAddresses();
NativeStackCallInfo[] resolvedStackCall = mi.getResolvedStackCall();
-
+
if (resolvedStackCall == null) {
return;
}
@@ -1130,18 +1134,18 @@ public final class NativeHeapPanel extends BaseHeapPanel {
if (addresses[i] == null || addresses[i].longValue() == 0) {
continue;
}
-
+
long addr = addresses[i].longValue();
NativeStackCallInfo source = resolvedStackCall[i];
-
+
TableItem item = new TableItem(mDetailTable, SWT.NONE);
item.setText(0, String.format("%08x", addr)); //$NON-NLS-1$
-
+
String libraryName = source.getLibraryName();
String methodName = source.getMethodName();
String sourceFile = source.getSourceFile();
int lineNumber = source.getLineNumber();
-
+
if (libraryName != null)
item.setText(1, libraryName);
if (methodName != null)
@@ -1373,9 +1377,11 @@ public final class NativeHeapPanel extends BaseHeapPanel {
public void widgetSelected(SelectionEvent e) {
// get the selection index
int index = mAllocationTable.getSelectionIndex();
- TableItem item = mAllocationTable.getItem(index);
- if (item != null && item.getData() instanceof NativeAllocationInfo) {
- fillDetailTable((NativeAllocationInfo)item.getData());
+ if (index >= 0 && index < mAllocationTable.getItemCount()) {
+ TableItem item = mAllocationTable.getItem(index);
+ if (item != null && item.getData() instanceof NativeAllocationInfo) {
+ fillDetailTable((NativeAllocationInfo)item.getData());
+ }
}
}
});
@@ -1437,9 +1443,11 @@ public final class NativeHeapPanel extends BaseHeapPanel {
// get the index in the library allocation table
int index2 = mLibraryAllocationTable.getSelectionIndex();
// get the MallocInfo object
- LibraryAllocations liballoc = mLibraryAllocations.get(index1);
- NativeAllocationInfo info = liballoc.getAllocation(index2);
- fillDetailTable(info);
+ if (index1 != -1 && index2 != -1) {
+ LibraryAllocations liballoc = mLibraryAllocations.get(index1);
+ NativeAllocationInfo info = liballoc.getAllocation(index2);
+ fillDetailTable(info);
+ }
}
});
@@ -1495,7 +1503,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
private void sortAllocationsPerLibrary() {
if (mClientData != null) {
mLibraryAllocations.clear();
-
+
// create a hash map of LibraryAllocations to access aggregate
// objects already created
HashMap<String, LibraryAllocations> libcache =
@@ -1617,11 +1625,11 @@ public final class NativeHeapPanel extends BaseHeapPanel {
return new PaletteData(colors);
}
-
+
private void saveAllocations(String fileName) {
try {
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
-
+
for (NativeAllocationInfo alloc : mAllocations) {
out.println(alloc.toString());
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
index d89acb5..1eaa4b9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
@@ -83,6 +83,8 @@ public class AdtConstants {
public final static String EXT_DEX = "dex"; //$NON-NLS-1$
/** Extension for temporary resource files, ie "ap_ */
public final static String EXT_RES = "ap_"; //$NON-NLS-1$
+ /** Extension for pre-processable images. Right now pngs */
+ public final static String EXT_PNG = "png"; //$NON-NLS-1$
private final static String DOT = "."; //$NON-NLS-1$
@@ -149,6 +151,10 @@ public class AdtConstants {
/** Absolute path of the resource folder, e.g. "/res".<br> This is a workspace path. */
public final static String WS_RESOURCES = WS_SEP + SdkConstants.FD_RESOURCES;
+ /** Absolute path of the crunch cache folder, e.g. "/bin/res".<br> This is a workspace path. */
+ public final static String WS_CRUNCHCACHE = WS_SEP + SdkConstants.FD_OUTPUT
+ + WS_SEP + SdkConstants.FD_RESOURCES;
+
/** Absolute path of the resource folder, e.g. "/assets".<br> This is a workspace path. */
public final static String WS_ASSETS = WS_SEP + SdkConstants.FD_ASSETS;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
index cdf48ad..3125c89 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.actions;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidPrintStream;
import com.android.ide.eclipse.adt.internal.build.BuildHelper;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -277,19 +278,20 @@ public class MultiApkExportAction implements IObjectActionDelegate {
// get the manifest file
IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
- // get the project bin folder
- IFolder projectBinFolder = wsRoot.getFolder(javaProject.getOutputLocation());
- String projectBinFolderPath = projectBinFolder.getLocation().toOSString();
+
+ // get the project bin folder for android files.
+ IFolder androidBinFolder = BaseProjectHelper.getAndroidOutputFolder(project);
+ String androidBinFolderPath = androidBinFolder.getLocation().toOSString();
// package the resources
helper.packageResources(manifestFile, libProjects,
softVariant != null ? softVariant.getValue() : null,
- compositeVersionCode, projectBinFolderPath, pkgName);
+ compositeVersionCode, androidBinFolderPath, pkgName);
apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName);
// do the final export.
- IFile dexFile = projectBinFolder.getFile(SdkConstants.FN_APK_CLASSES_DEX);
+ IFile dexFile = androidBinFolder.getFile(SdkConstants.FN_APK_CLASSES_DEX);
String outputFile = binFolder.getFile(outputName).getLocation().toOSString();
// get the list of referenced projects.
@@ -297,7 +299,7 @@ public class MultiApkExportAction implements IObjectActionDelegate {
List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(javaRefs);
helper.finalPackage(
- new File(projectBinFolderPath, pkgName).getAbsolutePath(),
+ new File(androidBinFolderPath, pkgName).getAbsolutePath(),
dexFile.getLocation().toOSString(),
outputFile,
javaProject,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java
index d777fcc..9af4780 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java
@@ -91,6 +91,8 @@ public class SdkManagerAction implements IWorkbenchWindowActionDelegate, IObject
// before the postInstallHook is called.
if (sdk != null) {
+ sdk.unloadTargetData(true /*preventReload*/);
+
DexWrapper dx = sdk.getDexWrapper();
dx.unload();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
index 5aa9647..1231de3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
@@ -26,17 +26,17 @@ import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkConstants;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.android.sdklib.SdkConstants;
import com.android.sdklib.build.ApkBuilder;
+import com.android.sdklib.build.ApkBuilder.JarStatus;
+import com.android.sdklib.build.ApkBuilder.SigningInfo;
import com.android.sdklib.build.ApkCreationException;
import com.android.sdklib.build.DuplicateFileException;
import com.android.sdklib.build.SealedApkException;
-import com.android.sdklib.build.ApkBuilder.JarStatus;
-import com.android.sdklib.build.ApkBuilder.SigningInfo;
import com.android.sdklib.internal.build.DebugKeyProvider;
-import com.android.sdklib.internal.build.SignedJarBuilder;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
+import com.android.sdklib.internal.build.SignedJarBuilder;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
@@ -96,12 +96,21 @@ public class BuildHelper {
private static final String CONSOLE_PREFIX_DX = "Dx"; //$NON-NLS-1$
private final static String TEMP_PREFIX = "android_"; //$NON-NLS-1$
+ private static final String COMMAND_CRUNCH = "crunch"; //$NON-NLS-1$
+ private static final String COMMAND_PACKAGE = "package"; //$NON-NLS-1$
+
private final IProject mProject;
private final AndroidPrintStream mOutStream;
private final AndroidPrintStream mErrStream;
private final boolean mVerbose;
private final boolean mDebugMode;
+ public static final boolean BENCHMARK_FLAG = false;
+ public static long sStartOverallTime = 0;
+ public static long sStartJavaCTime = 0;
+
+ private final static int MILLION = 1000000;
+
/**
* An object able to put a marker on a resource.
*/
@@ -126,6 +135,37 @@ public class BuildHelper {
mVerbose = verbose;
}
+
+ public void updateCrunchCache() throws AaptExecException, AaptResultException {
+ // Benchmarking start
+ long startCrunchTime = 0;
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Starting Initial Packaging (.ap_)"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ startCrunchTime = System.nanoTime();
+ }
+
+ // Get the resources folder to crunch from
+ IFolder resFolder = mProject.getFolder(AdtConstants.WS_RESOURCES);
+ List<String> resPaths = new ArrayList<String>();
+ resPaths.add(resFolder.getLocation().toOSString());
+
+ // Get the output folder where the cache is stored.
+ IFolder cacheFolder = mProject.getFolder(AdtConstants.WS_CRUNCHCACHE);
+ String cachePath = cacheFolder.getLocation().toOSString();
+
+ /* For crunching, we don't need the osManifestPath, osAssetsPath, or the configFilter
+ * parameters for executeAapt
+ */
+ executeAapt(COMMAND_CRUNCH, "", resPaths, "", cachePath, "", 0);
+
+ // Benchmarking end
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Ending Initial Package (.ap_). \nTime Elapsed: " //$NON-NLS-1$
+ + ((System.nanoTime() - startCrunchTime)/MILLION) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ }
+ }
/**
* Packages the resources of the projet into a .ap_ file.
* @param manifestFile the manifest of the project.
@@ -142,8 +182,20 @@ public class BuildHelper {
public void packageResources(IFile manifestFile, List<IProject> libProjects, String resFilter,
int versionCode, String outputFolder, String outputFilename)
throws AaptExecException, AaptResultException {
+
+ // Benchmarking start
+ long startPackageTime = 0;
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Starting Initial Packaging (.ap_)"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ startPackageTime = System.nanoTime();
+ }
+
// need to figure out some path before we can execute aapt;
+ // get the cache folder
+ IFolder cacheFolder = mProject.getFolder(AdtConstants.WS_CRUNCHCACHE);
+
// get the resource folder
IFolder resFolder = mProject.getFolder(AdtConstants.WS_RESOURCES);
@@ -155,12 +207,14 @@ public class BuildHelper {
assetsFolder = null;
}
+ IPath cacheLocation = cacheFolder.getLocation();
IPath resLocation = resFolder.getLocation();
IPath manifestLocation = manifestFile.getLocation();
if (resLocation != null && manifestLocation != null) {
// list of res folder (main project + maybe libraries)
ArrayList<String> osResPaths = new ArrayList<String>();
+ osResPaths.add(cacheLocation.toOSString()); // PNG crunch cache
osResPaths.add(resLocation.toOSString()); //main project
// libraries?
@@ -181,10 +235,17 @@ public class BuildHelper {
}
// build the default resource package
- executeAapt(osManifestPath, osResPaths, osAssetsPath,
+ executeAapt(COMMAND_PACKAGE, osManifestPath, osResPaths, osAssetsPath,
outputFolder + File.separator + outputFilename, resFilter,
versionCode);
}
+
+ // Benchmarking end
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Ending Initial Package (.ap_). \nTime Elapsed: " //$NON-NLS-1$
+ + ((System.nanoTime() - startPackageTime)/MILLION) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ }
}
/**
@@ -364,8 +425,13 @@ public class BuildHelper {
}
}
- public String[] getProjectOutputs() throws CoreException {
- IFolder outputFolder = BaseProjectHelper.getOutputFolder(mProject);
+ /**
+ * Return a list of the project output for compiled Java code.
+ * @return
+ * @throws CoreException
+ */
+ public String[] getProjectJavaOutputs() throws CoreException {
+ IFolder outputFolder = BaseProjectHelper.getJavaOutputFolder(mProject);
// get the list of referenced projects output to add
List<IProject> javaProjects = ProjectHelper.getReferencedProjects(mProject);
@@ -373,7 +439,7 @@ public class BuildHelper {
// get the project output, and since it's a new list object, just add the outputFolder
// of the project directly to it.
- List<String> projectOutputs = getProjectOutputs(referencedJavaProjects);
+ List<String> projectOutputs = getProjectJavaOutputs(referencedJavaProjects);
projectOutputs.add(0, outputFolder.getLocation().toOSString());
@@ -403,7 +469,7 @@ public class BuildHelper {
String[] compiledPaths;
if (includeProjectOutputs) {
- String[] projectOutputs = getProjectOutputs();
+ String[] projectOutputs = getProjectJavaOutputs();
compiledPaths = new String[libraries.length + projectOutputs.length];
@@ -677,10 +743,12 @@ public class BuildHelper {
/**
* Executes aapt. If any error happen, files or the project will be marked.
+ * @param command The command for aapt to execute. Currently supported: package and crunch
* @param osManifestPath The path to the manifest file
* @param osResPath The path to the res folder
* @param osAssetsPath The path to the assets folder. This can be null.
- * @param osOutFilePath The path to the temporary resource file to create.
+ * @param osOutFilePath The path to the temporary resource file to create,
+ * or in the case of crunching the path to the cache to create/update.
* @param configFilter The configuration filter for the resources to include
* (used with -c option, for example "port,en,fr" to include portrait, English and French
* resources.)
@@ -689,7 +757,7 @@ public class BuildHelper {
* @throws AaptExecException
* @throws AaptResultException
*/
- private void executeAapt(String osManifestPath,
+ private void executeAapt(String aaptCommand, String osManifestPath,
List<String> osResPaths, String osAssetsPath, String osOutFilePath,
String configFilter, int versionCode) throws AaptExecException, AaptResultException {
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
@@ -697,50 +765,58 @@ public class BuildHelper {
// Create the command line.
ArrayList<String> commandArray = new ArrayList<String>();
commandArray.add(target.getPath(IAndroidTarget.AAPT));
- commandArray.add("package"); //$NON-NLS-1$
- commandArray.add("-f");//$NON-NLS-1$
+ commandArray.add(aaptCommand);
if (AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE) {
commandArray.add("-v"); //$NON-NLS-1$
}
- // if more than one res, this means there's a library (or more) and we need
- // to activate the auto-add-overlay
- if (osResPaths.size() > 1) {
- commandArray.add("--auto-add-overlay"); //$NON-NLS-1$
+ // Common to all commands
+ for (String path : osResPaths) {
+ commandArray.add("-S"); //$NON-NLS-1$
+ commandArray.add(path);
}
- if (mDebugMode) {
- commandArray.add("--debug-mode"); //$NON-NLS-1$
- }
+ if (aaptCommand.equals(COMMAND_PACKAGE)) {
+ commandArray.add("-f"); //$NON-NLS-1$
+ commandArray.add("--no-crunch"); //$NON-NLS-1$
- if (versionCode > 0) {
- commandArray.add("--version-code"); //$NON-NLS-1$
- commandArray.add(Integer.toString(versionCode));
- }
+ // if more than one res, this means there's a library (or more) and we need
+ // to activate the auto-add-overlay
+ if (osResPaths.size() > 1) {
+ commandArray.add("--auto-add-overlay"); //$NON-NLS-1$
+ }
- if (configFilter != null) {
- commandArray.add("-c"); //$NON-NLS-1$
- commandArray.add(configFilter);
- }
+ if (mDebugMode) {
+ commandArray.add("--debug-mode"); //$NON-NLS-1$
+ }
- commandArray.add("-M"); //$NON-NLS-1$
- commandArray.add(osManifestPath);
+ if (versionCode > 0) {
+ commandArray.add("--version-code"); //$NON-NLS-1$
+ commandArray.add(Integer.toString(versionCode));
+ }
- for (String path : osResPaths) {
- commandArray.add("-S"); //$NON-NLS-1$
- commandArray.add(path);
- }
+ if (configFilter != null) {
+ commandArray.add("-c"); //$NON-NLS-1$
+ commandArray.add(configFilter);
+ }
- if (osAssetsPath != null) {
- commandArray.add("-A"); //$NON-NLS-1$
- commandArray.add(osAssetsPath);
- }
+ commandArray.add("-M"); //$NON-NLS-1$
+ commandArray.add(osManifestPath);
- commandArray.add("-I"); //$NON-NLS-1$
- commandArray.add(target.getPath(IAndroidTarget.ANDROID_JAR));
+ if (osAssetsPath != null) {
+ commandArray.add("-A"); //$NON-NLS-1$
+ commandArray.add(osAssetsPath);
+ }
+
+ commandArray.add("-I"); //$NON-NLS-1$
+ commandArray.add(target.getPath(IAndroidTarget.ANDROID_JAR));
- commandArray.add("-F"); //$NON-NLS-1$
- commandArray.add(osOutFilePath);
+ commandArray.add("-F"); //$NON-NLS-1$
+ commandArray.add(osOutFilePath);
+ } else if (aaptCommand.equals(COMMAND_CRUNCH)) {
+ commandArray.add("-C"); //$NON-NLS-1$
+ commandArray.add(osOutFilePath);
+ }
String command[] = commandArray.toArray(
new String[commandArray.size()]);
@@ -754,6 +830,15 @@ public class BuildHelper {
AdtPlugin.printToConsole(mProject, sb.toString());
}
+ // Benchmarking start
+ long startAaptTime = 0;
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Starting " + aaptCommand //$NON-NLS-1$
+ + " call to Aapt"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ startAaptTime = System.nanoTime();
+ }
+
// launch
int execError = 1;
try {
@@ -782,6 +867,14 @@ public class BuildHelper {
String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
throw new AaptExecException(msg, e);
}
+
+ // Benchmarking end
+ if (BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Ending " + aaptCommand //$NON-NLS-1$
+ + " call to Aapt.\nBENCHMARK ADT: Time Elapsed: " //$NON-NLS-1$
+ + ((System.nanoTime() - startAaptTime)/MILLION) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject, msg);
+ }
}
/**
@@ -964,7 +1057,7 @@ public class BuildHelper {
* @return a new list object containing the output folder paths.
* @throws CoreException
*/
- private List<String> getProjectOutputs(List<IJavaProject> referencedJavaProjects)
+ private List<String> getProjectJavaOutputs(List<IJavaProject> referencedJavaProjects)
throws CoreException {
ArrayList<String> list = new ArrayList<String>();
@@ -1085,8 +1178,15 @@ public class BuildHelper {
while (true) {
String line = outReader.readLine();
if (line != null) {
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
- project, line);
+ // If benchmarking always print the lines that
+ // correspond to benchmarking info returned by ADT
+ if (BENCHMARK_FLAG && line.startsWith("BENCHMARK:")) { //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS,
+ project, line);
+ } else {
+ AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
+ project, line);
+ }
} else {
break;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java
index 293b340..6b19cc5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java
@@ -17,8 +17,8 @@
package com.android.ide.eclipse.adt.internal.build.builders;
import com.android.ide.common.sdk.LoadStatus;
-import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.build.BuildHelper;
import com.android.ide.eclipse.adt.internal.build.Messages;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
index 1770df9..15aedaf 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
@@ -23,10 +23,10 @@ import com.android.ide.eclipse.adt.internal.build.AaptExecException;
import com.android.ide.eclipse.adt.internal.build.AaptParser;
import com.android.ide.eclipse.adt.internal.build.AaptResultException;
import com.android.ide.eclipse.adt.internal.build.BuildHelper;
-import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker;
import com.android.ide.eclipse.adt.internal.build.DexException;
import com.android.ide.eclipse.adt.internal.build.Messages;
import com.android.ide.eclipse.adt.internal.build.NativeLibInJarException;
+import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.ApkInstallManager;
@@ -69,6 +69,7 @@ public class PostCompilerBuilder extends BaseBuilder {
public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$
private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$
+ private static final String PROPERTY_UPDATE_CRUNCH_CACHE = "updateCrunchCache"; //$NON-NLS-1$
private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$
private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$
@@ -85,6 +86,13 @@ public class PostCompilerBuilder extends BaseBuilder {
private boolean mConvertToDex = false;
/**
+ * PNG Cache update flag. This is set to true if one of the changed/added/removed
+ * files is a .png file. Upon visiting all the delta resources, if this
+ * flag is true, then we know we'll have to update the PNG cache
+ */
+ private boolean mUpdateCrunchCache = false;
+
+ /**
* Package resources flag. This is set to true if one of the changed/added/removed
* file is a resource file. Upon visiting all the delta resource, if
* this flag is true, then we know we'll have to repackage the resources.
@@ -116,7 +124,6 @@ public class PostCompilerBuilder extends BaseBuilder {
mOutputFolder = javaProject.getOutputLocation();
mSourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
} catch (JavaModelException e) {
- } finally {
}
}
@@ -203,6 +210,20 @@ public class PostCompilerBuilder extends BaseBuilder {
// Clear the project of the generic markers
removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);
removeMarkersFromContainer(project, AdtConstants.MARKER_PACKAGING);
+
+ // also remove the files in the output folder (but not the Eclipse output folder).
+ IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(project);
+ IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(project);
+
+ if (javaOutput.equals(androidOutput) == false) {
+ // get the content
+ IResource[] members = androidOutput.members();
+ for (IResource member : members) {
+ if (member.equals(javaOutput) == false) {
+ member.delete(true /*force*/, monitor);
+ }
+ }
+ }
}
// build() returns a list of project from which this project depends for future compilation.
@@ -213,6 +234,18 @@ public class PostCompilerBuilder extends BaseBuilder {
// get a project object
IProject project = getProject();
+ // Benchmarking start
+ long startBuildTime = 0;
+ if (BuildHelper.BENCHMARK_FLAG) {
+ // End JavaC Timer
+ String msg = "BENCHMARK ADT: Ending Compilation \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
+ (System.nanoTime() - BuildHelper.sStartJavaCTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
+ msg = "BENCHMARK ADT: Starting PostCompilation"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
+ startBuildTime = System.nanoTime();
+ }
+
// list of referenced projects. This is a mix of java projects and library projects
// and is computed below.
IProject[] allRefProjects = null;
@@ -245,9 +278,8 @@ public class PostCompilerBuilder extends BaseBuilder {
// Top level check to make sure the build can move forward.
abortOnBadSetup(javaProject);
- // get the output folder, this method returns the path with a trailing
- // separator
- IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
+ // get the android output folder
+ IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project);
// now we need to get the classpath list
List<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject);
@@ -262,6 +294,7 @@ public class PostCompilerBuilder extends BaseBuilder {
AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project,
Messages.Start_Full_Apk_Build);
+ mUpdateCrunchCache = true;
mPackageResources = true;
mConvertToDex = true;
mBuildFinalPackage = true;
@@ -272,14 +305,16 @@ public class PostCompilerBuilder extends BaseBuilder {
// go through the resources and see if something changed.
IResourceDelta delta = getDelta(project);
if (delta == null) {
+ mUpdateCrunchCache = true;
mPackageResources = true;
mConvertToDex = true;
mBuildFinalPackage = true;
} else {
- dv = new PostCompilerDeltaVisitor(this, sourceList, outputFolder);
+ dv = new PostCompilerDeltaVisitor(this, sourceList, androidOutputFolder);
delta.accept(dv);
// save the state
+ mUpdateCrunchCache |= dv.getUpdateCrunchCache();
mPackageResources |= dv.getPackageResources();
mConvertToDex |= dv.getConvertToDex();
mBuildFinalPackage |= dv.getMakeFinalPackage();
@@ -327,6 +362,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// store the build status in the persistent storage
saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
+ saveProjectBooleanProperty(PROPERTY_UPDATE_CRUNCH_CACHE, mUpdateCrunchCache);
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
@@ -342,7 +378,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// remove older packaging markers.
removeMarkersFromContainer(javaProject.getProject(), AdtConstants.MARKER_PACKAGING);
- if (outputFolder == null) {
+ if (androidOutputFolder == null) {
// mark project and exit
markProject(AdtConstants.MARKER_PACKAGING, Messages.Failed_To_Get_Output,
IMarker.SEVERITY_ERROR);
@@ -377,7 +413,7 @@ public class PostCompilerBuilder extends BaseBuilder {
if (mPackageResources == false) {
// check the full resource package
- tmp = outputFolder.findMember(AdtConstants.FN_RESOURCES_AP_);
+ tmp = androidOutputFolder.findMember(AdtConstants.FN_RESOURCES_AP_);
if (tmp == null || tmp.exists() == false) {
mPackageResources = true;
mBuildFinalPackage = true;
@@ -386,7 +422,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// check classes.dex is present. If not we force to recreate it.
if (mConvertToDex == false) {
- tmp = outputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX);
+ tmp = androidOutputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX);
if (tmp == null || tmp.exists() == false) {
mConvertToDex = true;
mBuildFinalPackage = true;
@@ -396,7 +432,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// also check the final file(s)!
String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
if (mBuildFinalPackage == false) {
- tmp = outputFolder.findMember(finalPackageName);
+ tmp = androidOutputFolder.findMember(finalPackageName);
if (tmp == null || (tmp instanceof IFile &&
tmp.exists() == false)) {
String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
@@ -410,7 +446,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// because they are missing
// refresh the output directory first
- IContainer ic = outputFolder.getParent();
+ IContainer ic = androidOutputFolder.getParent();
if (ic != null) {
ic.refreshLocal(IResource.DEPTH_ONE, monitor);
}
@@ -443,18 +479,18 @@ public class PostCompilerBuilder extends BaseBuilder {
return allRefProjects;
}
- IPath binLocation = outputFolder.getLocation();
- if (binLocation == null) {
+ IPath androidBinLocation = androidOutputFolder.getLocation();
+ if (androidBinLocation == null) {
markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing,
IMarker.SEVERITY_ERROR);
return allRefProjects;
}
- String osBinPath = binLocation.toOSString();
+ String osAndroidBinPath = androidBinLocation.toOSString();
// Remove the old .apk.
// This make sure that if the apk is corrupted, then dx (which would attempt
// to open it), will not fail.
- String osFinalPackagePath = osBinPath + File.separator + finalPackageName;
+ String osFinalPackagePath = osAndroidBinPath + File.separator + finalPackageName;
File finalPackage = new File(osFinalPackagePath);
// if delete failed, this is not really a problem, as the final package generation
@@ -462,14 +498,39 @@ public class PostCompilerBuilder extends BaseBuilder {
// notified.
finalPackage.delete();
- // first we check if we need to package the resources.
+ // Check if we need to update the PNG cache
+ if (mUpdateCrunchCache) {
+ try {
+ helper.updateCrunchCache();
+ } catch (AaptExecException e) {
+ BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
+ e.getMessage(), IMarker.SEVERITY_ERROR);
+ return allRefProjects;
+ } catch (AaptResultException e) {
+ // attempt to parse the error output
+ String[] aaptOutput = e.getOutput();
+ boolean parsingError = AaptParser.parseOutput(aaptOutput, project);
+ // if we couldn't parse the output we display it in the console.
+ if (parsingError) {
+ AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput);
+ }
+ }
+
+ // crunch has been done. Reset state
+ mUpdateCrunchCache = false;
+
+ // and store it
+ saveProjectBooleanProperty(PROPERTY_UPDATE_CRUNCH_CACHE, mUpdateCrunchCache);
+ }
+
+ // Check if we need to package the resources.
if (mPackageResources) {
// remove some aapt_package only markers.
removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE);
try {
helper.packageResources(manifestFile, libProjects, null /*resfilter*/,
- 0 /*versionCode */, osBinPath,
+ 0 /*versionCode */, osAndroidBinPath,
AdtConstants.FN_RESOURCES_AP_);
} catch (AaptExecException e) {
BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING,
@@ -501,14 +562,16 @@ public class PostCompilerBuilder extends BaseBuilder {
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
}
+ String classesDexPath = osAndroidBinPath + File.separator +
+ SdkConstants.FN_APK_CLASSES_DEX;
+
// then we check if we need to package the .class into classes.dex
if (mConvertToDex) {
try {
String[] dxInputPaths = helper.getCompiledCodePaths(
true /*includeProjectOutputs*/, mResourceMarker);
- helper.executeDx(javaProject, dxInputPaths, osBinPath + File.separator +
- SdkConstants.FN_APK_CLASSES_DEX);
+ helper.executeDx(javaProject, dxInputPaths, classesDexPath);
} catch (DexException e) {
String message = e.getMessage();
@@ -539,11 +602,9 @@ public class PostCompilerBuilder extends BaseBuilder {
// and classes.dex.
// This is the default package with all the resources.
- String classesDexPath = osBinPath + File.separator +
- SdkConstants.FN_APK_CLASSES_DEX;
try {
helper.finalDebugPackage(
- osBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_,
+ osAndroidBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_,
classesDexPath, osFinalPackagePath,
javaProject, libProjects, referencedJavaProjects, mResourceMarker);
} catch (KeytoolException e) {
@@ -602,7 +663,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// we are done.
// get the resource to bin
- outputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
+ androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
// build has been done. reset the state of the builder
mBuildFinalPackage = false;
@@ -641,6 +702,17 @@ public class PostCompilerBuilder extends BaseBuilder {
markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
}
+ // Benchmarking end
+ if (BuildHelper.BENCHMARK_FLAG) {
+ String msg = "BENCHMARK ADT: Ending PostCompilation. \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
+ ((System.nanoTime() - startBuildTime)/Math.pow(10, 6)) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
+ // End Overall Timer
+ msg = "BENCHMARK ADT: Done with everything! \n BENCHMARK ADT: Time Elapsed: " + //$NON-NLS-1$
+ (System.nanoTime() - BuildHelper.sStartOverallTime)/Math.pow(10, 6) + "ms"; //$NON-NLS-1$
+ AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, project, msg);
+ }
+
return allRefProjects;
}
@@ -651,6 +723,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// load the build status. We pass true as the default value to
// force a recompile in case the property was not found
mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , true);
+ mUpdateCrunchCache = loadProjectBooleanProperty(PROPERTY_UPDATE_CRUNCH_CACHE, true);
mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true);
mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java
index 7e426fd..215cbb0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java
@@ -59,6 +59,13 @@ public class PostCompilerDeltaVisitor extends BaseDeltaVisitor
/**
* compile flag. This is set to true if one of the changed/added/removed
+ * files is a .png file. Upon visiting all the delta resources, if this
+ * flag is true, then we know we'll have to run "aapt crunch".
+ */
+ private boolean mUpdateCrunchCache = false;
+
+ /**
+ * compile flag. This is set to true if one of the changed/added/removed
* file is a resource file. Upon visiting all the delta resources, if
* this flag is true, then we know we'll have to make the intermediate
* apk file.
@@ -120,6 +127,10 @@ public class PostCompilerDeltaVisitor extends BaseDeltaVisitor
return mConvertToDex;
}
+ public boolean getUpdateCrunchCache() {
+ return mUpdateCrunchCache;
+ }
+
public boolean getPackageResources() {
return mPackageResources;
}
@@ -137,7 +148,7 @@ public class PostCompilerDeltaVisitor extends BaseDeltaVisitor
*/
public boolean visit(IResourceDelta delta) throws CoreException {
// if all flags are true, we can stop going through the resource delta.
- if (mConvertToDex && mPackageResources && mMakeFinalPackage) {
+ if (mConvertToDex && mUpdateCrunchCache && mPackageResources && mMakeFinalPackage) {
return false;
}
@@ -218,6 +229,12 @@ public class PostCompilerDeltaVisitor extends BaseDeltaVisitor
// (we don't care about folder being added/removed, only content
// is important)
if (type == IResource.FILE) {
+ // Check if this is a .png file. Any modification will
+ // trigger a cache update
+ String ext = resource.getFileExtension();
+ if (AdtConstants.EXT_PNG.equalsIgnoreCase(ext)) {
+ mUpdateCrunchCache = true;
+ }
mPackageResources = true;
mMakeFinalPackage = true;
return false;
@@ -225,7 +242,7 @@ public class PostCompilerDeltaVisitor extends BaseDeltaVisitor
// for folders, return true only if we don't already know we have to
// package the resources.
- return mPackageResources == false;
+ return mPackageResources == false || mUpdateCrunchCache == false;
} else if (mAssetPath != null && mAssetPath.isPrefixOf(path)) {
// this is the assets folder that was modified.
// we don't care what content was changed. All we care
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
index 943dcfe..672995f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
@@ -611,8 +611,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// handle libraries
ArrayList<IFolder> libResFolders = new ArrayList<IFolder>();
- ArrayList<IFolder> libOutputFolders = new ArrayList<IFolder>();
- ArrayList<String> libJavaPackages = new ArrayList<String>();
+ StringBuilder libJavaPackages = null;
if (libProjects != null) {
for (IProject lib : libProjects) {
IFolder libResFolder = lib.getFolder(SdkConstants.FD_RES);
@@ -623,26 +622,23 @@ public class PreCompilerBuilder extends BaseBuilder {
try {
String libJavaPackage = AndroidManifest.getPackage(new IFolderWrapper(lib));
if (libJavaPackage.equals(javaPackage) == false) {
- libJavaPackages.add(libJavaPackage);
- libOutputFolders.add(getGenManifestPackageFolder(libJavaPackage));
+ if (libJavaPackages == null) {
+ libJavaPackages = new StringBuilder(libJavaPackage);
+ } else {
+ libJavaPackages.append(":");
+ libJavaPackages.append(libJavaPackage);
+ }
}
} catch (Exception e) {
}
}
}
-
- execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath,
- mainPackageFolder, libResFolders, null /* custom java package */);
-
- final int count = libOutputFolders.size();
- if (count > 0) {
- for (int i = 0 ; i < count ; i++) {
- IFolder libFolder = libOutputFolders.get(i);
- String libJavaPackage = libJavaPackages.get(i);
- execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath,
- libFolder, libResFolders, libJavaPackage);
- }
+ String libPackages = null;
+ if (libJavaPackages != null) {
+ libPackages = libJavaPackages.toString();
}
+ execAapt(project, projectTarget, osOutputPath, osResPath, osManifestPath,
+ mainPackageFolder, libResFolders, libPackages);
}
}
@@ -659,13 +655,13 @@ public class PreCompilerBuilder extends BaseBuilder {
* If <var>customJavaPackage</var> is not null, this must match the new destination triggered
* by its value.
* @param libResFolders the list of res folders for the library.
- * @param customJavaPackage an optional javapackage to replace the main project java package.
+ * @param libraryPackages an optional list of javapackages to replace the main project java package.
* can be null.
* @throws AbortBuildException
*/
private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath,
String osResPath, String osManifestPath, IFolder packageFolder,
- ArrayList<IFolder> libResFolders, String customJavaPackage) throws AbortBuildException {
+ ArrayList<IFolder> libResFolders, String libraryPackages) throws AbortBuildException {
// We actually need to delete the manifest.java as it may become empty and
// in this case aapt doesn't generate an empty one, but instead doesn't
// touch it.
@@ -685,9 +681,9 @@ public class PreCompilerBuilder extends BaseBuilder {
array.add("--auto-add-overlay"); //$NON-NLS-1$
}
- if (customJavaPackage != null) {
- array.add("--custom-package"); //$NON-NLS-1$
- array.add(customJavaPackage);
+ if (libraryPackages != null) {
+ array.add("--extra-packages"); //$NON-NLS-1$
+ array.add(libraryPackages);
}
array.add("-J"); //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
index 38ab6d8..3f5a318 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
@@ -45,14 +45,12 @@ import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
-import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.source.ISourceViewer;
-import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.graphics.Image;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.w3c.dom.NamedNodeMap;
@@ -195,7 +193,6 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
return;
}
- int selectionLength = getSelectionLength(viewer);
int replaceLength = parent.length() - wordPrefix.length();
boolean isNew = replaceLength == 0 && nextNonspaceChar(viewer, offset) == '<';
// Special case: if we are right before the beginning of a new
@@ -214,21 +211,10 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
addMatchingProposals(proposals, choices, offset,
parentNode != null ? parentNode : null, wordPrefix, needTag,
false /* isAttribute */, isNew, false /*isComplete*/,
- selectionLength + replaceLength);
+ replaceLength);
}
}
- private int getSelectionLength(ITextViewer viewer) {
- // get the selection length
- int selectionLength = 0;
- ISelection selection = viewer.getSelectionProvider().getSelection();
- if (selection instanceof TextSelection) {
- TextSelection textSelection = (TextSelection) selection;
- selectionLength = textSelection.getLength();
- }
- return selectionLength;
- }
-
private void computeAttributeProposals(List<ICompletionProposal> proposals, ITextViewer viewer,
int offset, String wordPrefix, UiElementNode currentUiNode, Node parentNode,
Node currentNode, String parent, AttribInfo info, char nextChar) {
@@ -247,7 +233,6 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
return;
}
- int selectionLength = getSelectionLength(viewer);
int replaceLength = info.replaceLength;
if (info.correctedPrefix != null) {
wordPrefix = info.correctedPrefix;
@@ -259,7 +244,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
addMatchingProposals(proposals, choices, offset, parentNode != null ? parentNode : null,
wordPrefix, needTag, true /* isAttribute */, isNew, info.skipEndTag,
- selectionLength + replaceLength);
+ replaceLength);
}
private char computeElementNeedTag(ITextViewer viewer, int offset, String wordPrefix) {
@@ -541,20 +526,13 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
ISourceViewer viewer = mEditor.getStructuredSourceViewer();
char needTag = computeElementNeedTag(viewer, offset, wordPrefix);
- // get the selection length
- int selectionLength = 0;
- ISelection selection = viewer.getSelectionProvider().getSelection();
- if (selection instanceof TextSelection) {
- TextSelection textSelection = (TextSelection) selection;
- selectionLength = textSelection.getLength();
- }
int replaceLength = 0;
addMatchingProposals(proposals, choices,
offset, parentNode, wordPrefix, needTag,
false /* isAttribute */,
false /*isNew*/,
false /*isComplete*/,
- selectionLength + replaceLength);
+ replaceLength);
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
index 42c5344..bc54780 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
@@ -1080,9 +1080,9 @@ public class ConfigurationComposite extends Composite {
}
private ConfigMatch selectConfigMatch(List<ConfigMatch> matches) {
- // API 11-12: look for a x-large device
+ // API 11-13: look for a x-large device
int apiLevel = mProjectTarget.getVersion().getApiLevel();
- if (apiLevel >= 11 && apiLevel < 13) {
+ if (apiLevel >= 11 && apiLevel < 14) {
// TODO: Maybe check the compatible-screen tag in the manifest to figure out
// what kind of device should be used for display.
Collections.sort(matches, new TabletConfigComparator());
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
index ae41f84..ace5093 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
@@ -350,7 +350,6 @@ public class PreviewIconFactory {
}
AdtPlugin.log(IStatus.WARNING, "Failed to render set of icons for %1$s",
sb.toString());
- System.out.println(sb.toString());
if (session.getResult().getException() != null) {
AdtPlugin.log(session.getResult().getException(),
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java
index 54b827f..da764a8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java
@@ -139,10 +139,11 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
return;
}
- // make sure the project is built and PostCompilerBuilder runs.
+ // make sure the project and its dependencies are built
+ // and PostCompilerBuilder runs.
// This is a synchronous call which returns when the
// build is done.
- ProjectHelper.build(project, monitor, true);
+ ProjectHelper.build(project, monitor, true, true);
// check if the project has errors, and abort in this case.
if (ProjectHelper.hasError(project, true)) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java
index 8c4d08d..84f98a3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java
@@ -444,13 +444,14 @@ public final class BaseProjectHelper {
}
/**
- * Returns the {@link IFolder} representing the output for the project.
+ * Returns the {@link IFolder} representing the output for the project for Android specific
+ * files.
* <p>
* The project must be a java project and be opened, or the method will return null.
* @param project the {@link IProject}
* @return an IFolder item or null.
*/
- public final static IFolder getOutputFolder(IProject project) {
+ public final static IFolder getJavaOutputFolder(IProject project) {
try {
if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
// get a java project from the normal project object
@@ -470,4 +471,42 @@ public final class BaseProjectHelper {
}
return null;
}
+
+ /**
+ * Returns the {@link IFolder} representing the output for the project for compiled Java
+ * files.
+ * <p>
+ * The project must be a java project and be opened, or the method will return null.
+ * @param project the {@link IProject}
+ * @return an IFolder item or null.
+ */
+ public final static IFolder getAndroidOutputFolder(IProject project) {
+ try {
+ if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
+ // get a java project from the normal project object
+ IJavaProject javaProject = JavaCore.create(project);
+
+ IPath path = javaProject.getOutputLocation();
+ IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+ IResource outputResource = wsRoot.findMember(path);
+
+ if (outputResource != null) { // really shouldn't happen
+ // if the output folder is directly a child of the project,
+ // then use it directly.
+ if (outputResource.getParent().equals(project)) {
+ return (IFolder) outputResource;
+ }
+
+ // otherwise returns the parent folder of the java output folder.
+ return (IFolder) outputResource.getParent();
+ }
+ }
+ } catch (JavaModelException e) {
+ // Let's do nothing and return null
+ } catch (CoreException e) {
+ // Let's do nothing and return null
+ }
+ return null;
+ }
+
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
index 0312900..d172a0f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
@@ -34,18 +34,15 @@ import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.xml.AndroidManifest;
import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
@@ -83,8 +80,8 @@ public final class ExportHelper {
// the export, takes the output of the precompiler & Java builders so it's
// important to call build in case the auto-build option of the workspace is disabled.
- // Also enable post compilation
- ProjectHelper.build(project, monitor, true);
+ // Also enable post compilation and dependency building
+ ProjectHelper.build(project, monitor, true, true);
// if either key or certificate is null, ensure the other is null.
if (key == null) {
@@ -127,6 +124,9 @@ public final class ExportHelper {
File resourceFile = File.createTempFile(TEMP_PREFIX, AdtConstants.DOT_RES);
resourceFile.deleteOnExit();
+ // Make sure the PNG crunch cache is up to date
+ helper.updateCrunchCache();
+
// package the resources.
helper.packageResources(
project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML),
@@ -160,7 +160,7 @@ public final class ExportHelper {
if (runProguard) {
// the output of the main project (and any java-only project dependency)
- String[] projectOutputs = helper.getProjectOutputs();
+ String[] projectOutputs = helper.getProjectJavaOutputs();
// create a jar from the output of these projects
File inputJar = File.createTempFile(TEMP_PREFIX, AdtConstants.DOT_JAR);
@@ -268,64 +268,44 @@ public final class ExportHelper {
public static void exportUnsignedReleaseApk(final IProject project) {
Shell shell = Display.getCurrent().getActiveShell();
- // get the java project to get the output directory
- IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
- if (outputFolder != null) {
- IPath binLocation = outputFolder.getLocation();
-
- // make the full path to the package
- String fileName = project.getName() + AdtConstants.DOT_ANDROID_PACKAGE;
+ // create a default file name for the apk.
+ String fileName = project.getName() + AdtConstants.DOT_ANDROID_PACKAGE;
- File file = new File(binLocation.toOSString() + File.separator + fileName);
+ // Pop up the file save window to get the file location
+ FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
- if (file.exists() == false || file.isFile() == false) {
- MessageDialog.openError(Display.getCurrent().getActiveShell(),
- "Android IDE Plug-in",
- String.format("Failed to export %1$s: %2$s doesn't exist!",
- project.getName(), file.getPath()));
- return;
- }
+ fileDialog.setText("Export Project");
+ fileDialog.setFileName(fileName);
- // ok now pop up the file save window
- FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
-
- fileDialog.setText("Export Project");
- fileDialog.setFileName(fileName);
-
- final String saveLocation = fileDialog.open();
- if (saveLocation != null) {
- new Job("Android Release Export") {
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- try {
- exportReleaseApk(project,
- new File(saveLocation),
- null, //key
- null, //certificate
- monitor);
-
- // this is unsigned export. Let's tell the developers to run zip align
- AdtPlugin.displayWarning("Android IDE Plug-in", String.format(
- "An unsigned package of the application was saved at\n%1$s\n\n" +
- "Before publishing the application you will need to:\n" +
- "- Sign the application with your release key,\n" +
- "- run zipalign on the signed package. ZipAlign is located in <SDK>/tools/\n\n" +
- "Aligning applications allows Android to use application resources\n" +
- "more efficiently.", saveLocation));
-
- return Status.OK_STATUS;
- } catch (CoreException e) {
- AdtPlugin.displayError("Android IDE Plug-in", String.format(
- "Error exporting application:\n\n%1$s", e.getMessage()));
- return e.getStatus();
- }
+ final String saveLocation = fileDialog.open();
+ if (saveLocation != null) {
+ new Job("Android Release Export") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ exportReleaseApk(project,
+ new File(saveLocation),
+ null, //key
+ null, //certificate
+ monitor);
+
+ // this is unsigned export. Let's tell the developers to run zip align
+ AdtPlugin.displayWarning("Android IDE Plug-in", String.format(
+ "An unsigned package of the application was saved at\n%1$s\n\n" +
+ "Before publishing the application you will need to:\n" +
+ "- Sign the application with your release key,\n" +
+ "- run zipalign on the signed package. ZipAlign is located in <SDK>/tools/\n\n" +
+ "Aligning applications allows Android to use application resources\n" +
+ "more efficiently.", saveLocation));
+
+ return Status.OK_STATUS;
+ } catch (CoreException e) {
+ AdtPlugin.displayError("Android IDE Plug-in", String.format(
+ "Error exporting application:\n\n%1$s", e.getMessage()));
+ return e.getStatus();
}
- }.schedule();
- }
- } else {
- MessageDialog.openError(shell, "Android IDE Plug-in",
- String.format("Failed to export %1$s: Could not get project output location",
- project.getName()));
+ }
+ }.schedule();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java
index 394338b..4fd3c35 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java
@@ -16,8 +16,8 @@
package com.android.ide.eclipse.adt.internal.project;
-import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.SdkConstants;
@@ -66,6 +66,8 @@ public class FolderDecorator implements ILightweightLabelDecorator {
doDecoration(decoration, " [Generated Java Files]");
} else if (name.equals(SdkConstants.FD_NATIVE_LIBS)) {
doDecoration(decoration, null);
+ } else if (name.equals(SdkConstants.FD_OUTPUT)) {
+ doDecoration(decoration, null);
} else if (folder.isLinked() && Sdk.CREATOR_ADT.equals(
ProjectHelper.loadStringProperty(folder, Sdk.PROP_CREATOR))) {
doDecoration(decoration, " [Android Library]");
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java
index b1f3eb9..2804bac 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java
@@ -772,7 +772,7 @@ public final class ProjectHelper {
*/
public static IFile getApplicationPackage(IProject project) {
// get the output folder
- IFolder outputLocation = BaseProjectHelper.getOutputFolder(project);
+ IFolder outputLocation = BaseProjectHelper.getAndroidOutputFolder(project);
if (outputLocation == null) {
AdtPlugin.printErrorToConsole(project,
@@ -817,23 +817,43 @@ public final class ProjectHelper {
/**
* Build project incrementally. If fullBuild is not set, then the packaging steps in
* the post compiler are skipped. (Though resource deltas are still processed).
+ *
* @param project The project to be built.
* @param monitor A eclipse runtime progress monitor to be updated by the builders.
- * @param fullBuild Set whether to run the packaging (dexing and building apk) steps of
- * the post compiler.
+ * @param fullBuild Set whether to
+ * run the packaging (dexing and building apk) steps of the
+ * post compiler.
+ * @param buildDeps Set whether to run builders on the dependencies of the project
* @throws CoreException
*/
- public static void build(IProject project, IProgressMonitor monitor, boolean fullBuild)
+ public static void build(IProject project, IProgressMonitor monitor,
+ boolean fullBuild, boolean buildDeps)
throws CoreException {
+ // Get list of projects that we depend on
+ List<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
+ if (buildDeps) {
+ try {
+ androidProjectList = getAndroidProjectDependencies(
+ BaseProjectHelper.getJavaProject(project));
+ } catch (JavaModelException e) {
+ AdtPlugin.printErrorToConsole(project, e);
+ }
+ // Recursively build dependencies
+ for (IJavaProject dependency : androidProjectList) {
+ build(dependency.getProject(), monitor, fullBuild, true);
+ }
+ }
+
// Do an incremental build to pick up all the deltas
project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
- // If the preferences indicate not to use post compiler optimization then the
- // incremental build will have done everything necessary
+
+ // If the preferences indicate not to use post compiler optimization
+ // then the incremental build will have done everything necessary
if (fullBuild && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
// Create the map to pass to the PostC builder
Map<String, String> args = new TreeMap<String, String>();
args.put(PostCompilerBuilder.POST_C_REQUESTED, ""); //$NON-NLS-1$
- // Get Post Compiler to do packaging
+ // Get Post Compiler for this project
project.build(IncrementalProjectBuilder.FULL_BUILD,
PostCompilerBuilder.ID, args, monitor);
}
@@ -841,8 +861,10 @@ public final class ProjectHelper {
/**
* Build the project incrementally. Post compilation step will not occur.
+ * Projects that this project depends on will not be built.
* This is equivalent to calling
- * <code>build(project, monitor, false)</code>
+ * <code>build(project, monitor, false, false)</code>
+ *
* @param project The project to be built.
* @param monitor A eclipse runtime progress monitor to be updated by the builders.
* @throws CoreException
@@ -851,6 +873,6 @@ public final class ProjectHelper {
public static void build(IProject project, IProgressMonitor monitor)
throws CoreException {
// Disable full building by default
- build(project, monitor, false);
+ build(project, monitor, false, false);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
index 554d64d..05dede1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
@@ -134,13 +134,13 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
boolean mustSaveProp = false;
IAndroidTarget newTarget = mSelector.getSelected();
- if (newTarget != state.getTarget()) {
+ if (state == null || newTarget != state.getTarget()) {
mPropertiesWorkingCopy.setProperty(ProjectProperties.PROPERTY_TARGET,
newTarget.hashString());
mustSaveProp = true;
}
- if (mIsLibrary.getSelection() != state.isLibrary()) {
+ if (state == null || mIsLibrary.getSelection() != state.isLibrary()) {
mPropertiesWorkingCopy.setProperty(ProjectProperties.PROPERTY_LIBRARY,
Boolean.toString(mIsLibrary.getSelection()));
mustSaveProp = true;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java
index f2b9a55..8b2f729 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java
@@ -930,6 +930,8 @@ public class ExtractStringRefactoring extends Refactoring {
}
if (mReplaceAllJava) {
+ String currentIdentifier = mUnit != null ? mUnit.getHandleIdentifier() : ""; //$NON-NLS-1$
+
SubMonitor submon = SubMonitor.convert(monitor, 1);
for (ICompilationUnit unit : findAllJavaUnits()) {
// Only process Java compilation units that exist, are not derived
@@ -941,6 +943,13 @@ public class ExtractStringRefactoring extends Refactoring {
if (resource == null || resource.isDerived()) {
continue;
}
+
+ // Ensure that we don't process the current compilation unit (processed
+ // as mUnit above) twice
+ if (currentIdentifier.equals(unit.getHandleIdentifier())) {
+ continue;
+ }
+
ResourceAttributes attrs = resource.getResourceAttributes();
if (attrs != null && attrs.isReadOnly()) {
continue;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java
index 69f1267..c2177e3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java
@@ -383,4 +383,10 @@ public class AndroidTargetData {
mAttributeValues.remove(name);
mAttributeValues.put(name, values);
}
+
+ public void dispose() {
+ if (mLayoutLibrary != null) {
+ mLayoutLibrary.dispose();
+ }
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index 33d49a8..0002668 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -125,6 +125,12 @@ public final class Sdk {
private final HashMap<IAndroidTarget, TargetLoadBundle> mTargetDataStatusMap =
new HashMap<IAndroidTarget, TargetLoadBundle>();
+ /**
+ * If true the target data will never load anymore. The only way to reload them is to
+ * completely reload the SDK with {@link #loadSdk(String)}
+ */
+ private boolean mDontLoadTargetData = false;
+
private final String mDocBaseUrl;
private final LayoutDeviceManager mLayoutDeviceManager = new LayoutDeviceManager();
@@ -444,6 +450,10 @@ public final class Sdk {
boolean loadData = false;
synchronized (sLock) {
+ if (mDontLoadTargetData) {
+ return LoadStatus.FAILED;
+ }
+
TargetLoadBundle bundle = mTargetDataStatusMap.get(target);
if (bundle == null) {
bundle = new TargetLoadBundle();
@@ -622,6 +632,31 @@ public final class Sdk {
}
}
+ /**
+ * Unload the SDK's target data.
+ *
+ * If <var>preventReload</var>, this effect is final until the SDK instance is changed
+ * through {@link #loadSdk(String)}.
+ *
+ * The goal is to unload the targets to be able to replace existing targets with new ones,
+ * before calling {@link #loadSdk(String)} to fully reload the SDK.
+ *
+ * @param preventReload prevent the data from being loaded again for the remaining live of
+ * this {@link Sdk} instance.
+ */
+ public void unloadTargetData(boolean preventReload) {
+ synchronized (sLock) {
+ mDontLoadTargetData = preventReload;
+
+ // dispose of the target data.
+ for (AndroidTargetData data : mTargetDataMap.values()) {
+ data.dispose();
+ }
+
+ mTargetDataMap.clear();
+ }
+ }
+
private Sdk(SdkManager manager, DexWrapper dexWrapper, AvdManager avdManager) {
mManager = manager;
mDexWrapper = dexWrapper;
@@ -660,11 +695,18 @@ public final class Sdk {
monitor.removeFileListener(mFileListener);
monitor.removeResourceEventListener(mResourceEventListener);
- // the IAndroidTarget objects are now obsolete so update the project states.
synchronized (sLock) {
+ // the IAndroidTarget objects are now obsolete so update the project states.
for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) {
entry.getValue().setTarget(null);
}
+
+ // dispose of the target data.
+ for (AndroidTargetData data : mTargetDataMap.values()) {
+ data.dispose();
+ }
+
+ mTargetDataMap.clear();
}
}
@@ -838,6 +880,40 @@ public final class Sdk {
// of which can happen here since we're processing a Project opened event.
}
+ // convert older projects which use bin as the eclipse output folder into projects
+ // using bin/classes
+ IFolder javaOutput = BaseProjectHelper.getJavaOutputFolder(openedProject);
+ IFolder androidOutput = BaseProjectHelper.getAndroidOutputFolder(openedProject);
+ if (javaOutput.equals(androidOutput)) {
+ final IFolder newJavaOutput = javaOutput.getFolder(SdkConstants.FD_CLASSES_OUTPUT);
+ if (newJavaOutput.exists() == false) {
+ Job job = new Job("Project bin convertion") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ newJavaOutput.create(true /*force*/, true /*local*/,
+ monitor);
+
+ // set the java output to this project.
+ IJavaProject javaProject = JavaCore.create(openedProject);
+ javaProject.setOutputLocation(newJavaOutput.getFullPath(),
+ monitor);
+
+ openedProject.build(IncrementalProjectBuilder.CLEAN_BUILD, monitor);
+ } catch (CoreException e) {
+ return e.getStatus();
+ }
+
+ return Status.OK_STATUS;
+ }
+ };
+ job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
+ job.schedule();
+
+ }
+ }
+
+
ProjectState openedState = getProjectState(openedProject);
if (openedState != null) {
if (openedState.hasLibraries()) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java
index f7978fb..9ebdfbb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java
@@ -40,7 +40,6 @@ 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.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
@@ -55,7 +54,6 @@ final class ProjectCheckPage extends ExportWizardPage {
private final static String IMG_WARNING = "warning.png"; //$NON-NLS-1$
private final ExportWizard mWizard;
- private Display mDisplay;
private Image mError;
private Image mWarning;
private boolean mHasMessage = false;
@@ -76,7 +74,6 @@ final class ProjectCheckPage extends ExportWizardPage {
public void createControl(Composite parent) {
mProjectChooserHelper = new ProjectChooserHelper(parent.getShell(),
new NonLibraryProjectOnlyFilter());
- mDisplay = parent.getDisplay();
GridLayout gl = null;
GridData gd = null;
@@ -170,27 +167,12 @@ final class ProjectCheckPage extends ExportWizardPage {
}
// check the project output
- IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project);
- if (outputIFolder != null) {
- String outputOsPath = outputIFolder.getLocation().toOSString();
- String apkFilePath = outputOsPath + File.separator + project.getName() +
- AdtConstants.DOT_ANDROID_PACKAGE;
-
- File f = new File(apkFilePath);
- try {
- f.createNewFile();
- } catch (IOException e) {
- addError(mErrorComposite,
- String.format("Could not open %1$s/%2$s/%1$s%3$s for writing!",
- project.getName(), outputIFolder.getName(),
- AdtConstants.DOT_ANDROID_PACKAGE));
- }
- } else {
+ IFolder outputIFolder = BaseProjectHelper.getJavaOutputFolder(project);
+ if (outputIFolder == null) {
addError(mErrorComposite,
"Unable to get the output folder of the project!");
}
-
// project is an android project, we check the debuggable attribute.
ManifestData manifestData = AndroidManifestHelper.parseForData(project);
Boolean debuggable = null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java
index 9a3f572..6489616 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java
@@ -136,6 +136,9 @@ public class NewProjectWizard extends Wizard implements INewWizard {
private static final String BIN_DIRECTORY =
SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP;
+ private static final String BIN_CLASSES_DIRECTORY =
+ SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP +
+ SdkConstants.FD_CLASSES_OUTPUT + AdtConstants.WS_SEP;
private static final String RES_DIRECTORY =
SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP;
private static final String ASSETS_DIRECTORY =
@@ -190,7 +193,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$
private static final String[] DEFAULT_DIRECTORIES = new String[] {
- BIN_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
+ BIN_DIRECTORY, BIN_CLASSES_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
private static final String[] RES_DIRECTORIES = new String[] {
DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY };
private static final String[] RES_DENSITY_ENABLED_DIRECTORIES = new String[] {
@@ -706,7 +709,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
monitor);
// Set output location
- javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(),
+ javaProject.setOutputLocation(project.getFolder(BIN_CLASSES_DIRECTORY).getFullPath(),
monitor);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java
index 7608d2a..4eb54cd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java
@@ -432,6 +432,24 @@ public class AndroidContentAssistTest extends AdtProjectTest {
checkLayoutCompletion("completion9.xml", "^<Button");
}
+ public void testCompletion65() throws Exception {
+ // Test completion replacement when there is a selection
+ // (see issue http://code.google.com/p/android/issues/detail?id=18607 )
+ checkLayoutCompletion("completion10.xml", "\"[^wrap_content]\"");
+ }
+
+ public void testCompletion66() throws Exception {
+ checkResourceCompletion("completionvalues1.xml", "17[^sp]");
+ }
+
+ public void testCompletion67() throws Exception {
+ checkResourceCompletion("completionvalues1.xml", "17[^sp]");
+ }
+
+ public void testCompletion68() throws Exception {
+ checkResourceCompletion("completionvalues1.xml", "[^false]");
+ }
+
// ---- Test *applying* code completion ----
@@ -704,6 +722,21 @@ public class AndroidContentAssistTest extends AdtProjectTest {
"android:layout_marginRight");
}
+ public void testApplyCompletion42() throws Exception {
+ // Test completion replacement when there is a selection
+ // (see issue http://code.google.com/p/android/issues/detail?id=18607 )
+ checkApplyLayoutCompletion("completion10.xml", "\"[^wrap_content]\"", "fill_parent");
+ }
+
+ public void testApplyCompletion43() throws Exception {
+ // Same as testApplyCompletion42 but with a smaller selection range
+ checkApplyLayoutCompletion("completion10.xml", "\"[^wrap_c]ontent\"", "fill_parent");
+ }
+
+ public void testApplyCompletion44() throws Exception {
+ checkApplyResourceCompletion("completionvalues1.xml", "[^false]", "true");
+ }
+
// --- Code Completion test infrastructure ----
private void checkLayoutCompletion(String name, String caretLocation) throws Exception {
@@ -768,9 +801,6 @@ public class AndroidContentAssistTest extends AdtProjectTest {
private ICompletionProposal[] complete(IFile file, String caretLocation,
AndroidContentAssist assist) throws Exception {
- // Determine the offset
- int offset = getCaretOffset(file, caretLocation);
-
// Open file
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
assertNotNull(page);
@@ -779,6 +809,9 @@ public class AndroidContentAssistTest extends AdtProjectTest {
AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor;
ISourceViewer viewer = layoutEditor.getStructuredSourceViewer();
+ // Determine the offset, and possibly make text range selections as well
+ int offset = updateCaret(viewer, caretLocation);
+
// Run code completion
ICompletionProposal[] proposals = assist.computeCompletionProposals(viewer, offset);
if (proposals == null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
index 4baa0e7..013a6a0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
@@ -43,6 +43,7 @@ import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.wizard.IWizardContainer;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.graphics.Point;
@@ -274,18 +275,78 @@ public class AdtProjectTest extends SdkTestCase {
}
protected int getCaretOffset(String fileContent, String caretLocation) {
- assertTrue(caretLocation, caretLocation.contains("^"));
+ assertTrue(caretLocation, caretLocation.contains("^")); //$NON-NLS-1$
- int caretDelta = caretLocation.indexOf("^");
+ int caretDelta = caretLocation.indexOf("^"); //$NON-NLS-1$
assertTrue(caretLocation, caretDelta != -1);
- String caretContext = caretLocation.substring(0, caretDelta)
- + caretLocation.substring(caretDelta + 1); // +1: skip "^"
+
+ // String around caret/range without the range and caret marker characters
+ String caretContext;
+ if (caretLocation.contains("[^")) { //$NON-NLS-1$
+ caretDelta--;
+ assertTrue(caretLocation, caretLocation.startsWith("[^", caretDelta)); //$NON-NLS-1$
+ int caretRangeEnd = caretLocation.indexOf(']', caretDelta + 2);
+ assertTrue(caretLocation, caretRangeEnd != -1);
+ caretContext = caretLocation.substring(0, caretDelta)
+ + caretLocation.substring(caretDelta + 2, caretRangeEnd)
+ + caretLocation.substring(caretRangeEnd + 1);
+ } else {
+ caretContext = caretLocation.substring(0, caretDelta)
+ + caretLocation.substring(caretDelta + 1); // +1: skip "^"
+ }
+
int caretContextIndex = fileContent.indexOf(caretContext);
assertTrue("Caret content " + caretContext + " not found in file",
caretContextIndex != -1);
return caretContextIndex + caretDelta;
}
+ /**
+ * If the given caret location string contains a selection range, select that range in
+ * the given viewer
+ *
+ * @param viewer the viewer to contain the selection
+ * @param caretLocation the location string
+ */
+ protected int updateCaret(ISourceViewer viewer, String caretLocation) {
+ assertTrue(caretLocation, caretLocation.contains("^")); //$NON-NLS-1$
+
+ int caretDelta = caretLocation.indexOf("^"); //$NON-NLS-1$
+ assertTrue(caretLocation, caretDelta != -1);
+ String text = viewer.getTextWidget().getText();
+
+ int length = 0;
+
+ // String around caret/range without the range and caret marker characters
+ String caretContext;
+
+ if (caretLocation.contains("[^")) { //$NON-NLS-1$
+ caretDelta--;
+ assertTrue(caretLocation, caretLocation.startsWith("[^", caretDelta)); //$NON-NLS-1$
+
+ int caretRangeEnd = caretLocation.indexOf(']', caretDelta + 2);
+ assertTrue(caretLocation, caretRangeEnd != -1);
+ length = caretRangeEnd - caretDelta - 2;
+ assertTrue(length > 0);
+ caretContext = caretLocation.substring(0, caretDelta)
+ + caretLocation.substring(caretDelta + 2, caretRangeEnd)
+ + caretLocation.substring(caretRangeEnd + 1);
+ } else {
+ caretContext = caretLocation.substring(0, caretDelta)
+ + caretLocation.substring(caretDelta + 1); // +1: skip "^"
+ }
+
+ int caretContextIndex = text.indexOf(caretContext);
+
+ assertTrue("Caret content " + caretContext + " not found in file",
+ caretContextIndex != -1);
+
+ int offset = caretContextIndex + caretDelta;
+ viewer.setSelectedRange(offset, length);
+
+ return offset;
+ }
+
protected String addSelection(String newFileContents, Point selectedRange) {
int selectionBegin = selectedRange.x;
int selectionEnd = selectionBegin + selectedRange.y;
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-completion21.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-completion21.txt
index 96e8408..9b7d001 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-completion21.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-completion21.txt
@@ -1,4 +1,5 @@
Code completion in broken2.xml for style=^:
+"@style/"
"@android:"
"@drawable/"
"@layout/"
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion11.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion11.txt
index 099a779..701c608 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion11.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion11.txt
@@ -15,7 +15,6 @@ Code completion in completion1.xml for ^<TextView:
<ExpandableListView ></ExpandableListView>
<FrameLayout ></FrameLayout>
<Gallery />
-<GestureOverlayView /> : GestureOverlayView specific attributes.
<GridView ></GridView>
<HorizontalScrollView ></HorizontalScrollView>
<ImageButton />
@@ -57,5 +56,6 @@ Code completion in completion1.xml for ^<TextView:
<WebView />
<ZoomButton />
<ZoomControls />
+<android.gesture.GestureOverlayView ></android.gesture.GestureOverlayView> : GestureOverlayView specific attributes.
<fragment /> : A Fragment is a piece of an application's user interface or behavior that can be placed in an Activity
<include /> : Lets you statically include XML layouts inside other XML layouts.
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion12.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion12.txt
index 6d20513..9bf7169 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion12.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion12.txt
@@ -15,7 +15,6 @@ Code completion in completion1.xml for btn_default">^</FrameLayout>:
<ExpandableListView ></ExpandableListView>
<FrameLayout ></FrameLayout>
<Gallery />
-<GestureOverlayView /> : GestureOverlayView specific attributes.
<GridView ></GridView>
<HorizontalScrollView ></HorizontalScrollView>
<ImageButton />
@@ -57,5 +56,6 @@ Code completion in completion1.xml for btn_default">^</FrameLayout>:
<WebView />
<ZoomButton />
<ZoomControls />
+<android.gesture.GestureOverlayView ></android.gesture.GestureOverlayView> : GestureOverlayView specific attributes.
<fragment /> : A Fragment is a piece of an application's user interface or behavior that can be placed in an Activity
<include /> : Lets you statically include XML layouts inside other XML layouts.
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion6.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion6.txt
index 0853a83..99f92b8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion6.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion6.txt
@@ -3,6 +3,7 @@ Code completion in completion1.xml for style="@android:^style/Widget.Button":
@android:anim/
@android:animator/
@android:array/
+@android:attr/
@android:bool/
@android:color/
@android:declare-styleable/
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-applyCompletion42.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-applyCompletion42.diff
new file mode 100644
index 0000000..dcd7f71
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-applyCompletion42.diff
@@ -0,0 +1,4 @@
+Code completion in completion10.xml for "[^wrap_content]" selecting fill_parent:
+< android:layout_height="^wrap_content"/>
+---
+> android:layout_height="fill_parent"^/>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-applyCompletion43.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-applyCompletion43.diff
new file mode 100644
index 0000000..a8d2d43
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-applyCompletion43.diff
@@ -0,0 +1,4 @@
+Code completion in completion10.xml for "[^wrap_c]ontent" selecting fill_parent:
+< android:layout_height="^wrap_content"/>
+---
+> android:layout_height="fill_parent"^/>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-completion65.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-completion65.txt
new file mode 100644
index 0000000..69fae0d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10-expected-completion65.txt
@@ -0,0 +1,4 @@
+Code completion in completion10.xml for "[^wrap_content]":
+fill_parent
+match_parent
+wrap_content
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10.xml
new file mode 100644
index 0000000..57b38ee
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion10.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical">
+<Gallery xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/gallery"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+</RelativeLayout>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion9-expected-completion64.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion9-expected-completion64.txt
index 8fc4636..e889a29 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion9-expected-completion64.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion9-expected-completion64.txt
@@ -15,7 +15,6 @@ Code completion in completion9.xml for ^<Button:
<ExpandableListView ></ExpandableListView>
<FrameLayout ></FrameLayout>
<Gallery />
-<GestureOverlayView /> : GestureOverlayView specific attributes.
<GridView ></GridView>
<HorizontalScrollView ></HorizontalScrollView>
<ImageButton />
@@ -57,6 +56,7 @@ Code completion in completion9.xml for ^<Button:
<WebView />
<ZoomButton />
<ZoomControls />
+<android.gesture.GestureOverlayView ></android.gesture.GestureOverlayView> : GestureOverlayView specific attributes.
<fragment /> : A Fragment is a piece of an application's user interface or behavior that can be placed in an Activity
<include /> : Lets you statically include XML layouts inside other XML layouts.
<merge ></merge> : A root tag useful for XML layouts inflated using a ViewStub.
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion44.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion44.diff
new file mode 100644
index 0000000..7a845b3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion44.diff
@@ -0,0 +1,4 @@
+Code completion in completionvalues1.xml for [^false] selecting true:
+< <item name="android:alwaysDrawnWithCache"> ^false </item>
+---
+> <item name="android:alwaysDrawnWithCache"> true^</item>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion66.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion66.txt
new file mode 100644
index 0000000..4f29a8b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion66.txt
@@ -0,0 +1,7 @@
+Code completion in completionvalues1.xml for 17[^sp]:
+17dp : <b>Density-independent Pixels</b> - an abstract unit that is based on the physical density of the screen.
+17sp : <b>Scale-independent Pixels</b> - this is like the dp unit, but it is also scaled by the user's font size preference.
+17pt : <b>Points</b> - 1/72 of an inch based on the physical size of the screen.
+17mm : <b>Millimeters</b> - based on the physical size of the screen.
+17in : <b>Inches</b> - based on the physical size of the screen.
+17px : <b>Pixels</b> - corresponds to actual pixels on the screen. Not recommended.
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion67.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion67.txt
new file mode 100644
index 0000000..4f29a8b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion67.txt
@@ -0,0 +1,7 @@
+Code completion in completionvalues1.xml for 17[^sp]:
+17dp : <b>Density-independent Pixels</b> - an abstract unit that is based on the physical density of the screen.
+17sp : <b>Scale-independent Pixels</b> - this is like the dp unit, but it is also scaled by the user's font size preference.
+17pt : <b>Points</b> - 1/72 of an inch based on the physical size of the screen.
+17mm : <b>Millimeters</b> - based on the physical size of the screen.
+17in : <b>Inches</b> - based on the physical size of the screen.
+17px : <b>Pixels</b> - corresponds to actual pixels on the screen. Not recommended.
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion68.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion68.txt
new file mode 100644
index 0000000..5aa1d43
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion68.txt
@@ -0,0 +1,3 @@
+Code completion in completionvalues1.xml for [^false]:
+true
+false
diff --git a/files/ant/main_rules.xml b/files/ant/main_rules.xml
index 97e9efb..ff19add 100644
--- a/files/ant/main_rules.xml
+++ b/files/ant/main_rules.xml
@@ -83,7 +83,9 @@
<property name="out.absolute.dir" location="${out.dir}" />
<property name="out.classes.dir" value="${out.absolute.dir}/classes" />
<property name="out.classes.absolute.dir" location="${out.classes.dir}" />
-
+ <property name="out.resource.dir" location="${out.dir}/res" />
+ <property name="out.resource.absolute.dir" location="${out.resource.dir}" />
+
<!-- Intermediate files -->
<property name="dex.file.name" value="classes.dex" />
<property name="intermediate.dex.file"
@@ -160,6 +162,9 @@
</and>
</condition>
+ <!-- properties for packaging -->
+ <property name="build.packaging.nocrunch" value="true" />
+
<!-- Tools -->
<condition property="exe" value=".exe" else=""><os family="windows" /></condition>
<property name="adb" location="${android.platform.tools.dir}/adb${exe}" />
@@ -492,12 +497,24 @@
</if>
</target>
+<!-- Updates the pre-processed PNG cache -->
+ <target name="-crunch">
+ <exec executable="${aapt}">
+ <arg value="crunch" />
+ <arg value="-v" />
+ <arg value="-S" />
+ <arg path="${resource.absolute.dir}" />
+ <arg value="-C" />
+ <arg path="${out.resource.absolute.dir}" />
+ </exec>
+ </target>
+
<!-- Puts the project's resources into the output package file
This actually can create multiple resource package in case
Some custom apk with specific configuration have been
declared in default.properties.
-->
- <target name="-package-resources">
+ <target name="-package-resources" depends="-crunch">
<echo>Packaging resources</echo>
<aapt executable="${aapt}"
command="package"
@@ -507,8 +524,10 @@
assets="${asset.absolute.dir}"
androidjar="${android.jar}"
apkfolder="${out.absolute.dir}"
+ nocrunch="${build.packaging.nocrunch}"
resourcefilename="${resource.package.file.name}"
resourcefilter="${aapt.resource.filter}">
+ <res path="${out.resource.absolute.dir}" />
<res path="${resource.absolute.dir}" />
<!-- <nocompress /> forces no compression on any files in assets or res/raw -->
<!-- <nocompress extension="xml" /> forces no compression on specific file extensions in assets and res/raw -->
diff --git a/files/devices.xml b/files/devices.xml
index ca26416..6dc0a3f 100644
--- a/files/devices.xml
+++ b/files/devices.xml
@@ -315,6 +315,34 @@
</d:config>
</d:device>
+ <d:device name="7in WSVGA (Tablet)">
+ <d:default>
+ <d:screen-size>large</d:screen-size>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:pixel-density>mdpi</d:pixel-density>
+ <d:touch-type>finger</d:touch-type>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:text-input-method>nokeys</d:text-input-method>
+ <d:nav-state>navexposed</d:nav-state>
+ <d:nav-method>nonav</d:nav-method>
+ <d:screen-dimension>
+ <d:size>1024</d:size>
+ <d:size>600</d:size>
+ </d:screen-dimension>
+ <d:xdpi>169</d:xdpi>
+ <d:ydpi>169</d:ydpi>
+ </d:default>
+
+ <d:config name="Landscape">
+ <d:screen-orientation>land</d:screen-orientation>
+ </d:config>
+ <d:config name="Portrait">
+ <d:screen-orientation>port</d:screen-orientation>
+ </d:config>
+ </d:device>
+
+
<d:device name="10.1in WXGA (Tablet)">
<d:default>
<d:screen-size>xlarge</d:screen-size>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
index c1ac84b..467529d 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
@@ -226,13 +226,20 @@ final class PlatformTarget implements IAndroidTarget {
}
public String getDefaultSkin() {
+ // only one skin? easy.
if (mSkins.length == 1) {
return mSkins[0];
}
+ // look for the skin name in the platform props
+ String skinName = mProperties.get(SdkConstants.PROP_SDK_DEFAULT_SKIN);
+ if (skinName != null) {
+ return skinName;
+ }
+
+ // otherwise try to find a good default.
if (mVersion.getApiLevel() >= 4) {
- // at this time, this is the default skin for all the platforms.
- // TODO: make it configurable using a file in the platform folder.
+ // at this time, this is the default skin for all older platforms that had 2+ skins.
return "WVGA800";
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
index bac2464..713e8a8 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
@@ -182,6 +182,8 @@ public final class SdkConstants {
public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$
/** Default output folder name, i.e. "bin" */
public final static String FD_OUTPUT = "bin"; //$NON-NLS-1$
+ /** Classes output folder name, i.e. "classes" */
+ public final static String FD_CLASSES_OUTPUT = "classes"; //$NON-NLS-1$
/** proguard output folder for mapping, etc.. files */
public final static String FD_PROGUARD = "proguard"; //$NON-NLS-1$
@@ -357,6 +359,9 @@ public final class SdkConstants {
/** SDK property: ant templates revision */
public final static String PROP_SDK_ANT_TEMPLATES_REVISION = "sdk.ant.templates.revision"; //$NON-NLS-1$
+ /** SDK property: default skin */
+ public final static String PROP_SDK_DEFAULT_SKIN = "sdk.skin.default"; //$NON-NLS-1$
+
/* Android Class Constants */
public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
index 645c2de..12645af 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
@@ -32,6 +32,7 @@ import org.w3c.dom.Node;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
@@ -70,6 +71,44 @@ public class AddonPackage extends Package
public String getDescription() {
return mDescription;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());
+ result = prime * result + ((mName == null) ? 0 : mName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Lib)) {
+ return false;
+ }
+ Lib other = (Lib) obj;
+ if (mDescription == null) {
+ if (other.mDescription != null) {
+ return false;
+ }
+ } else if (!mDescription.equals(other.mDescription)) {
+ return false;
+ }
+ if (mName == null) {
+ if (other.mName != null) {
+ return false;
+ }
+ } else if (!mName.equals(other.mName)) {
+ return false;
+ }
+ return true;
+ }
}
private final Lib[] mLibs;
@@ -114,7 +153,12 @@ public class AddonPackage extends Package
@VisibleForTesting(visibility=Visibility.PRIVATE)
protected AddonPackage(IAndroidTarget target, Properties props) {
- super( null, //source
+ this(null /*source*/, target, props);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected AddonPackage(SdkSource source, IAndroidTarget target, Properties props) {
+ super( source, //source
props, //properties
target.getRevision(), //revision
null, //license
@@ -384,4 +428,62 @@ public class AddonPackage extends Package
return false;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
+ result = prime * result + Arrays.hashCode(mLibs);
+ result = prime * result + ((mName == null) ? 0 : mName.hashCode());
+ result = prime * result + ((mVendor == null) ? 0 : mVendor.hashCode());
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof AddonPackage)) {
+ return false;
+ }
+ AddonPackage other = (AddonPackage) obj;
+ if (mLayoutlibVersion == null) {
+ if (other.mLayoutlibVersion != null) {
+ return false;
+ }
+ } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
+ return false;
+ }
+ if (!Arrays.equals(mLibs, other.mLibs)) {
+ return false;
+ }
+ if (mName == null) {
+ if (other.mName != null) {
+ return false;
+ }
+ } else if (!mName.equals(other.mName)) {
+ return false;
+ }
+ if (mVendor == null) {
+ if (other.mVendor != null) {
+ return false;
+ }
+ } else if (!mVendor.equals(other.mVendor)) {
+ return false;
+ }
+ if (mVersion == null) {
+ if (other.mVersion != null) {
+ return false;
+ }
+ } else if (!mVersion.equals(other.mVersion)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
index 4a21851..fb28a9d 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
@@ -384,4 +384,92 @@ public class Archive implements IDescription, Comparable<Archive> {
}
return 0;
}
+
+ /**
+ * Note: An {@link Archive}'s hash code does NOT depend on the parent {@link Package} hash code.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mArch == null) ? 0 : mArch.hashCode());
+ result = prime * result + ((mChecksum == null) ? 0 : mChecksum.hashCode());
+ result = prime * result + ((mChecksumType == null) ? 0 : mChecksumType.hashCode());
+ result = prime * result + (mIsLocal ? 1231 : 1237);
+ result = prime * result + ((mLocalOsPath == null) ? 0 : mLocalOsPath.hashCode());
+ result = prime * result + ((mOs == null) ? 0 : mOs.hashCode());
+ result = prime * result + (int) (mSize ^ (mSize >>> 32));
+ result = prime * result + ((mUrl == null) ? 0 : mUrl.hashCode());
+ return result;
+ }
+
+ /**
+ * Note: An {@link Archive}'s equality does NOT depend on the parent {@link Package} equality.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Archive)) {
+ return false;
+ }
+ Archive other = (Archive) obj;
+ if (mArch == null) {
+ if (other.mArch != null) {
+ return false;
+ }
+ } else if (!mArch.equals(other.mArch)) {
+ return false;
+ }
+ if (mChecksum == null) {
+ if (other.mChecksum != null) {
+ return false;
+ }
+ } else if (!mChecksum.equals(other.mChecksum)) {
+ return false;
+ }
+ if (mChecksumType == null) {
+ if (other.mChecksumType != null) {
+ return false;
+ }
+ } else if (!mChecksumType.equals(other.mChecksumType)) {
+ return false;
+ }
+ if (mIsLocal != other.mIsLocal) {
+ return false;
+ }
+ if (mLocalOsPath == null) {
+ if (other.mLocalOsPath != null) {
+ return false;
+ }
+ } else if (!mLocalOsPath.equals(other.mLocalOsPath)) {
+ return false;
+ }
+ if (mOs == null) {
+ if (other.mOs != null) {
+ return false;
+ }
+ } else if (!mOs.equals(other.mOs)) {
+ return false;
+ }
+ if (mSize != other.mSize) {
+ return false;
+ }
+ if (mUrl == null) {
+ if (other.mUrl != null) {
+ return false;
+ }
+ } else if (!mUrl.equals(other.mUrl)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
index 5171454..b598f7d 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
@@ -253,4 +253,34 @@ public class DocPackage extends Package implements IPackageVersion {
// not an upgrade but not incompatible either.
return UpdateInfo.NOT_UPDATE;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof DocPackage)) {
+ return false;
+ }
+ DocPackage other = (DocPackage) obj;
+ if (mVersion == null) {
+ if (other.mVersion != null) {
+ return false;
+ }
+ } else if (!mVersion.equals(other.mVersion)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
index 793c98d..ac8dd09 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
@@ -16,6 +16,8 @@
package com.android.sdklib.internal.repository;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.NullSdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
@@ -27,6 +29,7 @@ import org.w3c.dom.Node;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
@@ -159,7 +162,8 @@ public class ExtraPackage extends MinToolsPackage
}
}
- private ExtraPackage(SdkSource source,
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected ExtraPackage(SdkSource source,
Properties props,
String vendor,
String path,
@@ -561,4 +565,50 @@ public class ExtraPackage extends MinToolsPackage
return null;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + mMinApiLevel;
+ result = prime * result + ((mPath == null) ? 0 : mPath.hashCode());
+ result = prime * result + Arrays.hashCode(mProjectFiles);
+ result = prime * result + ((mVendor == null) ? 0 : mVendor.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof ExtraPackage)) {
+ return false;
+ }
+ ExtraPackage other = (ExtraPackage) obj;
+ if (mMinApiLevel != other.mMinApiLevel) {
+ return false;
+ }
+ if (mPath == null) {
+ if (other.mPath != null) {
+ return false;
+ }
+ } else if (!mPath.equals(other.mPath)) {
+ return false;
+ }
+ if (!Arrays.equals(mProjectFiles, other.mProjectFiles)) {
+ return false;
+ }
+ if (mVendor == null) {
+ if (other.mVendor != null) {
+ return false;
+ }
+ } else if (!mVendor.equals(other.mVendor)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java
index 88b778d..32d3ef3 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java
@@ -97,4 +97,34 @@ public class LayoutlibVersionMixin implements ILayoutlibVersion {
public Pair<Integer, Integer> getLayoutlibVersion() {
return mLayoutlibVersion;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof LayoutlibVersionMixin)) {
+ return false;
+ }
+ LayoutlibVersionMixin other = (LayoutlibVersionMixin) obj;
+ if (mLayoutlibVersion == null) {
+ if (other.mLayoutlibVersion != null) {
+ return false;
+ }
+ } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java
index 661a73c..56f6247 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java
@@ -98,4 +98,30 @@ public abstract class MinToolsPackage extends Package implements IMinToolsDepend
props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(getMinToolsRevision()));
}
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + mMinToolsRevision;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof MinToolsPackage)) {
+ return false;
+ }
+ MinToolsPackage other = (MinToolsPackage) obj;
+ if (mMinToolsRevision != other.mMinToolsRevision) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
index a9c5996..fe083d1 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
@@ -28,6 +28,7 @@ import org.w3c.dom.Node;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
@@ -450,6 +451,15 @@ public abstract class Package implements IDescription, Comparable<Package> {
}
/**
+ * A package is local (that is 'installed locally') if it contains a single
+ * archive that is local. If not local, it's a remote package, only available
+ * on a remote source for download and installation.
+ */
+ public boolean isLocal() {
+ return mArchives.length == 1 && mArchives[0].isLocal();
+ }
+
+ /**
* Computes a potential installation folder if an archive of this package were
* to be installed right away in the given SDK root.
* <p/>
@@ -651,4 +661,49 @@ public abstract class Package implements IDescription, Comparable<Package> {
return sb.toString();
}
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(mArchives);
+ result = prime * result + ((mObsolete == null) ? 0 : mObsolete.hashCode());
+ result = prime * result + mRevision;
+ result = prime * result + ((mSource == null) ? 0 : mSource.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Package)) {
+ return false;
+ }
+ Package other = (Package) obj;
+ if (!Arrays.equals(mArchives, other.mArchives)) {
+ return false;
+ }
+ if (mObsolete == null) {
+ if (other.mObsolete != null) {
+ return false;
+ }
+ } else if (!mObsolete.equals(other.mObsolete)) {
+ return false;
+ }
+ if (mRevision != other.mRevision) {
+ return false;
+ }
+ if (mSource == null) {
+ if (other.mSource != null) {
+ return false;
+ }
+ } else if (!mSource.equals(other.mSource)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
index c66f34c..6035ef5 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
@@ -90,7 +90,12 @@ public class PlatformPackage extends MinToolsPackage implements IPackageVersion,
@VisibleForTesting(visibility=Visibility.PRIVATE)
protected PlatformPackage(IAndroidTarget target, Properties props) {
- super( null, //source
+ this(null /*source*/, target, props);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected PlatformPackage(SdkSource source, IAndroidTarget target, Properties props) {
+ super( source, //source
props, //properties
target.getRevision(), //revision
null, //license
@@ -256,4 +261,51 @@ public class PlatformPackage extends MinToolsPackage implements IPackageVersion,
return false;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result +
+ ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ result = prime * result + ((mVersionName == null) ? 0 : mVersionName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof PlatformPackage)) {
+ return false;
+ }
+ PlatformPackage other = (PlatformPackage) obj;
+ if (mLayoutlibVersion == null) {
+ if (other.mLayoutlibVersion != null) {
+ return false;
+ }
+ } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
+ return false;
+ }
+ if (mVersion == null) {
+ if (other.mVersion != null) {
+ return false;
+ }
+ } else if (!mVersion.equals(other.mVersion)) {
+ return false;
+ }
+ if (mVersionName == null) {
+ if (other.mVersionName != null) {
+ return false;
+ }
+ } else if (!mVersionName.equals(other.mVersionName)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java
index d4622ad..0b57eb1 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java
@@ -157,6 +157,22 @@ public class SdkSources {
}
/**
+ * Each source keeps a local cache of whatever it loaded recently.
+ * This calls {@link SdkSource#clearPackages()} on all the available sources,
+ * and the next call to {@link SdkSource#getPackages()} will actually reload
+ * the remote package list.
+ */
+ public void clearAllPackages() {
+ synchronized(mSources) {
+ for (ArrayList<SdkSource> list : mSources.values()) {
+ for (SdkSource source : list) {
+ source.clearPackages();
+ }
+ }
+ }
+ }
+
+ /**
* Returns the category of a given source, or null if the source is unknown.
* <p/>
* Note that this method uses object identity to find a given source, and does
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
index 69039ea..bac1253 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
@@ -352,4 +352,30 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency
// get the return code from the process
return process.waitFor();
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + mMinPlatformToolsRevision;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof ToolPackage)) {
+ return false;
+ }
+ ToolPackage other = (ToolPackage) obj;
+ if (mMinPlatformToolsRevision != other.mMinPlatformToolsRevision) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java
index 1279529..61febb1 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java
@@ -31,12 +31,32 @@ public class MockAddonPackage extends AddonPackage {
/**
* Creates a {@link MockAddonTarget} with the requested base platform and addon revision
- * and then a {@link MockAddonPackage} wrapping it.
+ * and then a {@link MockAddonPackage} wrapping it and a default name of "addon".
*
* By design, this package contains one and only one archive.
*/
public MockAddonPackage(MockPlatformPackage basePlatform, int revision) {
- super(new MockAddonTarget(basePlatform.getTarget(), revision), null /*props*/);
+ this("addon", basePlatform, revision); //$NON-NLS-1$
+ }
+
+ /**
+ * Creates a {@link MockAddonTarget} with the requested base platform and addon revision
+ * and then a {@link MockAddonPackage} wrapping it.
+ *
+ * By design, this package contains one and only one archive.
+ */
+ public MockAddonPackage(String name, MockPlatformPackage basePlatform, int revision) {
+ super(new MockAddonTarget(name, basePlatform.getTarget(), revision), null /*props*/);
+ }
+
+ public MockAddonPackage(
+ SdkSource source,
+ String name,
+ MockPlatformPackage basePlatform,
+ int revision) {
+ super(source,
+ new MockAddonTarget(name, basePlatform.getTarget(), revision),
+ null /*props*/);
}
/**
@@ -47,8 +67,10 @@ public class MockAddonPackage extends AddonPackage {
private final IAndroidTarget mParentTarget;
private final int mRevision;
+ private final String mName;
- public MockAddonTarget(IAndroidTarget parentTarget, int revision) {
+ public MockAddonTarget(String name, IAndroidTarget parentTarget, int revision) {
+ mName = name;
mParentTarget = parentTarget;
mRevision = revision;
}
@@ -85,10 +107,6 @@ public class MockAddonPackage extends AddonPackage {
return "";
}
- public String getName() {
- return "mock addon target";
- }
-
public IOptionalLibrary[] getOptionalLibraries() {
return null;
}
@@ -133,14 +151,18 @@ public class MockAddonPackage extends AddonPackage {
return 0;
}
- public String getVendor() {
- return null;
- }
-
public AndroidVersion getVersion() {
return mParentTarget.getVersion();
}
+ public String getName() {
+ return mName;
+ }
+
+ public String getVendor() {
+ return mParentTarget.getVendor();
+ }
+
public String getVersionName() {
return String.format("mock-addon-%1$d", getVersion().getApiLevel());
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockExtraPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockExtraPackage.java
new file mode 100755
index 0000000..086e27e
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockExtraPackage.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 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.repository;
+
+import com.android.sdklib.internal.repository.Archive.Arch;
+import com.android.sdklib.internal.repository.Archive.Os;
+
+import java.util.Properties;
+
+/**
+ * A mock {@link ExtraPackage} for testing.
+ *
+ * By design, this package contains one and only one archive.
+ */
+public class MockExtraPackage extends ExtraPackage {
+
+ /**
+ * Creates a {@link MockExtraPackage} with the given revision and hardcoded defaults
+ * for everything else.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MockExtraPackage(String vendor, String path, int revision, int min_platform_tools_rev) {
+ this(null /*source*/, vendor, path, revision, min_platform_tools_rev);
+ }
+
+ public MockExtraPackage(SdkSource source,
+ String vendor, String path, int revision, int min_platform_tools_rev) {
+ super(
+ source,
+ createProps(min_platform_tools_rev), // props,
+ vendor,
+ path,
+ revision,
+ null, // license,
+ "desc", // description,
+ "url", // descUrl,
+ Os.getCurrentOs(), // archiveOs,
+ Arch.getCurrentArch(), // archiveArch,
+ "foo" // archiveOsPath
+ );
+ }
+
+ private static Properties createProps(int min_platform_tools_rev) {
+ Properties props = new Properties();
+ props.setProperty(ToolPackage.PROP_MIN_PLATFORM_TOOLS_REV,
+ Integer.toString((min_platform_tools_rev)));
+ return props;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java
index d3c1235..975108c 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java
@@ -39,7 +39,7 @@ public class MockPlatformPackage extends PlatformPackage {
* By design, this package contains one and only one archive.
*/
public MockPlatformPackage(int apiLevel, int revision) {
- this(new MockPlatformTarget(apiLevel, revision), null /*props*/);
+ this(null /*source*/, new MockPlatformTarget(apiLevel, revision), null /*props*/);
}
/**
@@ -51,12 +51,18 @@ public class MockPlatformPackage extends PlatformPackage {
* By design, this package contains one and only one archive.
*/
public MockPlatformPackage(int apiLevel, int revision, int min_tools_rev) {
- this(new MockPlatformTarget(apiLevel, revision), createProps(min_tools_rev));
+ this(null /*source*/,
+ new MockPlatformTarget(apiLevel, revision),
+ createProps(min_tools_rev));
+ }
+
+ public MockPlatformPackage(SdkSource source, int apiLevel, int revision, int min_tools_rev) {
+ this(source, new MockPlatformTarget(apiLevel, revision), createProps(min_tools_rev));
}
/** A little trick to be able to capture the target new after passing it to the super. */
- private MockPlatformPackage(IAndroidTarget target, Properties props) {
- super(target, props);
+ private MockPlatformPackage(SdkSource source, IAndroidTarget target, Properties props) {
+ super(source, target, props);
mTarget = target;
}
@@ -82,7 +88,6 @@ public class MockPlatformPackage extends PlatformPackage {
public MockPlatformTarget(int apiLevel, int revision) {
mApiLevel = apiLevel;
mRevision = revision;
-
}
public String getClasspathName() {
@@ -117,10 +122,6 @@ public class MockPlatformPackage extends PlatformPackage {
return "";
}
- public String getName() {
- return "mock platform target";
- }
-
public IOptionalLibrary[] getOptionalLibraries() {
return null;
}
@@ -165,8 +166,20 @@ public class MockPlatformPackage extends PlatformPackage {
return 0;
}
+ /**
+ * Returns a vendor that depends on the parent *platform* API.
+ * This works well in Unit Tests where we'll typically have different
+ * platforms as unique identifiers.
+ */
public String getVendor() {
- return null;
+ return "vendor " + Integer.toString(mApiLevel);
+ }
+
+ /**
+ * Create a synthetic name using the target API level.
+ */
+ public String getName() {
+ return "platform r" + Integer.toString(mApiLevel);
}
public AndroidVersion getVersion() {
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformToolPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformToolPackage.java
index 0befa80..788fdf7 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformToolPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformToolPackage.java
@@ -33,8 +33,18 @@ public class MockPlatformToolPackage extends PlatformToolPackage {
* By design, this creates a package with one and only one archive.
*/
public MockPlatformToolPackage(int revision) {
+ this(null /*source*/, revision);
+ }
+
+ /**
+ * Creates a {@link MockPlatformToolPackage} with the given revision and hardcoded defaults
+ * for everything else.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MockPlatformToolPackage(SdkSource source, int revision) {
super(
- null, // source,
+ source, // source,
null, // props,
revision,
null, // license,
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockToolPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockToolPackage.java
index 8ce704c..7cbf802 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockToolPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockToolPackage.java
@@ -35,8 +35,18 @@ public class MockToolPackage extends ToolPackage {
* By design, this creates a package with one and only one archive.
*/
public MockToolPackage(int revision, int min_platform_tools_rev) {
+ this(null /*source*/, revision, min_platform_tools_rev);
+ }
+
+ /**
+ * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults
+ * for everything else.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MockToolPackage(SdkSource source, int revision, int min_platform_tools_rev) {
super(
- null, // source,
+ source, // source,
createProps(min_platform_tools_rev), // props,
revision,
null, // license,
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java
index 6a6b423..925f088 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java
@@ -20,7 +20,6 @@ import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.IPackageVersion;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.LocalSdkParser;
import com.android.sdklib.internal.repository.Package;
import com.android.sdklib.internal.repository.SdkSource;
import com.android.sdklib.internal.repository.Package.UpdateInfo;
@@ -30,7 +29,6 @@ import org.eclipse.swt.widgets.Shell;
import java.io.File;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -45,10 +43,9 @@ class PackageLoader {
* Interface for the callback called by
* {@link PackageLoader#loadPackages(ISourceLoadedCallback)}.
* <p/>
- * After processing each source, the package loader calls {@link #onSourceLoaded(List)}
- * with the list of package items found in that source. The client should process that
- * list as it want, typically by accumulating the package items in a list of its own.
- * By returning true from {@link #onSourceLoaded(List)}, the client tells the loader to
+ * After processing each source, the package loader calls {@link #onUpdateSource}
+ * with the list of packages found in that source.
+ * By returning true from {@link #onUpdateSource}, the client tells the loader to
* continue and process the next source. By returning false, it tells to stop loading.
* <p/>
* The {@link #onLoadCompleted()} method is guaranteed to be called at the end, no
@@ -58,20 +55,19 @@ class PackageLoader {
public interface ISourceLoadedCallback {
/**
* After processing each source, the package loader calls this method with the
- * list of package items found in that source. The client should process that
- * list as it want, typically by accumulating the package items in a list of its own.
- * By returning true from {@link #onSourceLoaded(List)}, the client tells the loader to
- * continue and process the next source. By returning false, it tells to stop loading.
+ * list of packages found in that source.
+ * By returning true from {@link #onUpdateSource}, the client tells
+ * the loader to continue and process the next source.
+ * By returning false, it tells to stop loading.
* <p/>
- * <em>Important</em>: This method is called from a sub-thread, so clients who try
- * to access any UI widgets must wrap their calls into {@link Display#syncExec(Runnable)}
- * or {@link Display#asyncExec(Runnable)}.
+ * <em>Important</em>: This method is called from a sub-thread, so clients which
+ * try to access any UI widgets must wrap their calls into
+ * {@link Display#syncExec(Runnable)} or {@link Display#asyncExec(Runnable)}.
*
- * @param pkgItems All the package items loaded from the last processed source.
- * This is a copy and the client can hold to this list or modify it in any way.
+ * @param packages All the packages loaded from the source. Never null.
* @return True if the load operation should continue, false if it should stop.
*/
- public boolean onSourceLoaded(List<PkgItem> pkgItems);
+ public boolean onUpdateSource(SdkSource source, Package[] packages);
/**
* This method is guaranteed to be called at the end, no matter how the
@@ -142,15 +138,15 @@ class PackageLoader {
}
// get local packages and offer them to the callback
- final List<PkgItem> allPkgItems = loadLocalPackages();
- if (!allPkgItems.isEmpty()) {
- // Notify the callback by giving it a copy of the current list.
- // (in case the callback holds to the list... we still need this list of
- // ourselves below).
- if (!sourceLoadedCallback.onSourceLoaded(new ArrayList<PkgItem>(allPkgItems))) {
- return;
- }
+ Package[] localPkgs = mUpdaterData.getInstalledPackages();
+ if (localPkgs == null) {
+ localPkgs = new Package[0];
}
+ if (!sourceLoadedCallback.onUpdateSource(null, localPkgs)) {
+ return;
+ }
+
+ final int[] numPackages = { localPkgs == null ? 0 : localPkgs.length };
// get remote packages
final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp();
@@ -169,30 +165,11 @@ class PackageLoader {
continue;
}
- List<PkgItem> sourcePkgItems = new ArrayList<PkgItem>();
-
- nextPkg: for(Package pkg : pkgs) {
- boolean isUpdate = false;
- for (PkgItem pi: allPkgItems) {
- if (pi.isSamePackageAs(pkg)) {
- continue nextPkg;
- }
- if (pi.isUpdatedBy(pkg)) {
- isUpdate = true;
- break;
- }
- }
-
- if (!isUpdate) {
- PkgItem pi = new PkgItem(pkg, PkgState.NEW);
- sourcePkgItems.add(pi);
- allPkgItems.add(pi);
- }
- }
+ numPackages[0] += pkgs.length;
// Notify the callback a new source has finished loading.
// If the callback requests so, stop right away.
- if (!sourceLoadedCallback.onSourceLoaded(sourcePkgItems)) {
+ if (!sourceLoadedCallback.onUpdateSource(source, pkgs)) {
return;
}
}
@@ -200,7 +177,7 @@ class PackageLoader {
monitor.logError("Loading source failed: %1$s", e.toString());
} finally {
monitor.setDescription("Done loading %1$d packages from %2$d sources",
- allPkgItems.size(),
+ numPackages[0],
sources.length);
}
}
@@ -211,26 +188,6 @@ class PackageLoader {
}
/**
- * Internal method that returns all installed packages from the {@link LocalSdkParser}
- * associated with the {@link UpdaterData}.
- * <p/>
- * Note that the {@link LocalSdkParser} maintains a cache, so callers need to clear
- * it if they know they changed the local installation.
- *
- * @return A new list of {@link PkgItem}. May be empty but never null.
- */
- private List<PkgItem> loadLocalPackages() {
- List<PkgItem> pkgItems = new ArrayList<PkgItem>();
-
- for (Package pkg : mUpdaterData.getInstalledPackages()) {
- PkgItem pi = new PkgItem(pkg, PkgState.INSTALLED);
- pkgItems.add(pi);
- }
-
- return pkgItems;
- }
-
- /**
* Load packages, source by source using {@link #loadPackages(ISourceLoadedCallback)},
* and executes the given {@link IAutoInstallTask} on the current package list.
* That is for each package known, the install task is queried to find if
@@ -260,64 +217,44 @@ class PackageLoader {
public void loadPackagesWithInstallTask(final IAutoInstallTask installTask) {
loadPackages(new ISourceLoadedCallback() {
- public boolean onSourceLoaded(List<PkgItem> pkgItems) {
- for (PkgItem item : pkgItems) {
- Package acceptedPkg = null;
- switch(item.getState()) {
- case NEW:
- if (installTask.acceptPackage(item.getPackage())) {
- acceptedPkg = item.getPackage();
- }
- break;
- case HAS_UPDATE:
- for (Package upd : item.getUpdatePkgs()) {
- if (installTask.acceptPackage(upd)) {
- acceptedPkg = upd;
- break;
- }
- }
- break;
- case INSTALLED:
- if (installTask.acceptPackage(item.getPackage())) {
+ public boolean onUpdateSource(SdkSource source, Package[] packages) {
+ for (Package pkg : packages) {
+ if (pkg.isLocal()) {
+ // This is a local (aka installed) package
+ if (installTask.acceptPackage(pkg)) {
// If the caller is accepting an installed package,
// return a success and give the package's install path
- acceptedPkg = item.getPackage();
- Archive[] a = acceptedPkg.getArchives();
+ Archive[] a = pkg.getArchives();
// an installed package should have one local compatible archive
if (a.length == 1 && a[0].isCompatible()) {
installTask.setResult(
- acceptedPkg,
+ pkg,
true /*success*/,
new File(a[0].getLocalOsPath()));
-
- // return false to tell loadPackages() that we don't
- // need to continue processing any more sources.
- return false;
}
+ // return false to tell loadPackages() that we don't
+ // need to continue processing any more sources.
+ return false;
}
- }
- if (acceptedPkg != null) {
- // Try to install this package if it has one compatible archive.
- Archive archiveToInstall = null;
+ } else {
+ // This is a remote package
+ if (installTask.acceptPackage(pkg)) {
+ // The caller is accepting this remote package. Let's try to install it.
- for (Archive a2 : acceptedPkg.getArchives()) {
- if (a2.isCompatible()) {
- archiveToInstall = a2;
- break;
+ for (Archive archive : pkg.getArchives()) {
+ if (archive.isCompatible()) {
+ installArchive(archive);
+ break;
+ }
}
+ // return false to tell loadPackages() that we don't
+ // need to continue processing any more sources.
+ return false;
}
-
- if (archiveToInstall != null) {
- installArchive(archiveToInstall);
- }
-
- // return false to tell loadPackages() that we don't
- // need to continue processing any more sources.
- return false;
}
-
}
+
// Tell loadPackages() to process the next source.
return true;
}
@@ -362,11 +299,10 @@ class PackageLoader {
// The local package list has changed, make sure to refresh it
mUpdaterData.getLocalSdkParser().clearPackages();
- final List<PkgItem> localPkgItems = loadLocalPackages();
+ final Package[] localPkgs = mUpdaterData.getInstalledPackages();
// Try to locate the installed package in the new package list
- for (PkgItem localItem : localPkgItems) {
- Package localPkg = localItem.getPackage();
+ for (Package localPkg : localPkgs) {
if (localPkg.canBeUpdatedBy(packageToInstall) == UpdateInfo.NOT_UPDATE) {
Archive[] localArchive = localPkg.getArchives();
if (localArchive.length == 1 && localArchive[0].isCompatible()) {
@@ -397,184 +333,217 @@ class PackageLoader {
*/
public enum PkgState {
/**
- * Package is locally installed and has no update available on remote sites.
+ * Package is locally installed and may or may not have an update.
*/
INSTALLED,
/**
- * Package is installed and has an update available.
- * In this case, {@link PkgItem#getUpdatePkgs()} provides the list of 1 or more
- * packages that can update this {@link PkgItem}.
- * <p/>
- * Although not structurally enforced, it can be reasonably expected that
- * the original package and the updating packages all come from the same source.
- */
- HAS_UPDATE,
-
- /**
- * There's a new package available on the remote site that isn't
- * installed locally.
+ * There's a new package available on the remote site that isn't installed locally.
*/
NEW
}
/**
- * A {@link PkgItem} represents one {@link Package} combined with its state.
+ * A {@link PkgItem} represents one main {@link Package} combined with its state
+ * and an optional update package.
* <p/>
- * It can be either a locally installed package, or a remotely available package.
- * If the later, it can be either a new package or an update for a locally installed one.
- * <p/>
- * In the case of an update, the {@link PkgItem#getPackage()} represents the currently
- * installed package and there's a separate list of {@link PkgItem#getUpdatePkgs()} that
- * links to the updating packages. Note that in a typical repository there should only
- * one update for a given installed package however the system is designed to be more
- * generic and allow many.
+ * The main package is final and cannot change since it's what "defines" this PkgItem.
+ * The state or update package can change later.
*/
public static class PkgItem implements Comparable<PkgItem> {
- private final Package mPkg;
private PkgState mState;
- private List<Package> mUpdatePkgs;
+ private final Package mMainPkg;
+ private Package mUpdatePkg;
- public PkgItem(Package pkg, PkgState state) {
- mPkg = pkg;
+ /**
+ * Create a new {@link PkgItem} for this main package.
+ * The main package is final and cannot change since it's what "defines" this PkgItem.
+ * The state or update package can change later.
+ */
+ public PkgItem(Package mainPkg, PkgState state) {
+ mMainPkg = mainPkg;
mState = state;
- assert mPkg != null;
+ assert mMainPkg != null;
}
public boolean isObsolete() {
- return mPkg.isObsolete();
+ return mMainPkg.isObsolete();
}
- public boolean isSameItemAs(PkgItem item) {
- boolean same = this.mState == item.mState;
- if (same) {
- same = isSamePackageAs(item.getPackage());
- }
- // check updating packages are the same
- if (same) {
- List<Package> u1 = getUpdatePkgs();
- List<Package> u2 = item.getUpdatePkgs();
- same = (u1 == null && u2 == null) ||
- (u1 != null && u2 != null);
- if (same && u1 != null && u2 != null) {
- int n = u1.size();
- same = n == u2.size();
- if (same) {
- for (int i = 0; same && i < n; i++) {
- Package p1 = u1.get(i);
- Package p2 = u2.get(i);
- same = p1.canBeUpdatedBy(p2) == UpdateInfo.NOT_UPDATE;
- }
- }
- }
- }
-
- return same;
+ public Package getUpdatePkg() {
+ return mUpdatePkg;
}
- public boolean isSamePackageAs(Package pkg) {
- return mPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE;
- }
-
- /**
- * Check whether the 'pkg' argument updates this package.
- * If it does, record it as a sub-package.
- * Returns true if it was recorded as an update, false otherwise.
- */
- public boolean isUpdatedBy(Package pkg) {
- if (mPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
- if (mUpdatePkgs == null) {
- mUpdatePkgs = new ArrayList<Package>();
- }
- mUpdatePkgs.add(pkg);
- mState = PkgState.HAS_UPDATE;
- return true;
- }
-
- return false;
+ public boolean hasUpdatePkg() {
+ return mUpdatePkg != null;
}
public String getName() {
- return mPkg.getListDescription();
+ return mMainPkg.getListDescription();
}
public int getRevision() {
- return mPkg.getRevision();
+ return mMainPkg.getRevision();
}
public String getDescription() {
- return mPkg.getDescription();
+ return mMainPkg.getDescription();
}
- public Package getPackage() {
- return mPkg;
+ public Package getMainPackage() {
+ return mMainPkg;
}
public PkgState getState() {
return mState;
}
+ public void setState(PkgState state) {
+ mState = state;
+ }
+
public SdkSource getSource() {
- if (mState == PkgState.NEW) {
- return mPkg.getParentSource();
- } else {
- return null;
- }
+ return mMainPkg.getParentSource();
}
public int getApi() {
- return mPkg instanceof IPackageVersion ?
- ((IPackageVersion) mPkg).getVersion().getApiLevel() :
+ return mMainPkg instanceof IPackageVersion ?
+ ((IPackageVersion) mMainPkg).getVersion().getApiLevel() :
-1;
}
- public List<Package> getUpdatePkgs() {
- return mUpdatePkgs;
- }
-
public Archive[] getArchives() {
- return mPkg.getArchives();
+ return mMainPkg.getArchives();
}
public int compareTo(PkgItem pkg) {
- return getPackage().compareTo(pkg.getPackage());
+ return getMainPackage().compareTo(pkg.getMainPackage());
}
/**
- * Returns true if this package or any of the updating packages contains
+ * Returns true if this package or its updating packages contains
* the exact given archive.
* Important: This compares object references, not object equality.
*/
public boolean hasArchive(Archive archive) {
- if (mPkg.hasArchive(archive)) {
+ if (mMainPkg.hasArchive(archive)) {
return true;
}
- if (mUpdatePkgs != null && !mUpdatePkgs.isEmpty()) {
- for (Package p : mUpdatePkgs) {
- if (p.hasArchive(archive)) {
- return true;
- }
+ if (mUpdatePkg != null && mUpdatePkg.hasArchive(archive)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the main packages are of the same type and are
+ * not an update of each other.
+ */
+ public boolean isSameMainPackageAs(Package pkg) {
+ if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
+ // package revision numbers must match
+ return mMainPkg.getRevision() == pkg.getRevision();
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether too {@link PkgItem} are the same.
+ * This checks both items have the same state, both main package are similar
+ * and that they have the same updating packages.
+ */
+ public boolean isSameItemAs(PkgItem item) {
+ if (this == item) {
+ return true;
+ }
+ boolean same = this.mState == item.mState;
+ if (same) {
+ same = isSameMainPackageAs(item.getMainPackage());
+ }
+
+ if (same) {
+ // check updating packages are the same
+ Package p1 = this.mUpdatePkg;
+ Package p2 = item.getUpdatePkg();
+ same = (p1 == p2) || (p1 == null && p2 == null) || (p1 != null && p2 != null);
+
+ if (same && p1 != null) {
+ same = p1.canBeUpdatedBy(p2) == UpdateInfo.NOT_UPDATE;
}
}
+
+ return same;
+ }
+
+ /**
+ * Equality is defined as {@link #isSameItemAs(PkgItem)}: state, main package
+ * and update package must be the similar.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof PkgItem) && this.isSameItemAs((PkgItem) obj);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mState == null) ? 0 : mState.hashCode());
+ result = prime * result + ((mMainPkg == null) ? 0 : mMainPkg.hashCode());
+ result = prime * result + ((mUpdatePkg == null) ? 0 : mUpdatePkg.hashCode());
+ return result;
+ }
+
+ /**
+ * Check whether the 'pkg' argument is an update for this package.
+ * If it is, record it as an updating package.
+ * If there's already an updating package, only keep the most recent update.
+ * Returns true if it is update (even if there was already an update and this
+ * ended up not being the most recent), false if incompatible or not an update.
+ *
+ * This should only be used for installed packages.
+ */
+ public boolean mergeUpdate(Package pkg) {
+ if (mUpdatePkg == pkg) {
+ return true;
+ }
+ if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
+ if (mUpdatePkg == null) {
+ mUpdatePkg = pkg;
+ } else if (mUpdatePkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
+ // If we have more than one, keep only the most recent update
+ mUpdatePkg = pkg;
+ }
+ return true;
+ }
+
return false;
}
+ public void removeUpdate() {
+ mUpdatePkg = null;
+ }
+
/** Returns a string representation of this item, useful when debugging. */
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(mState.toString());
+ StringBuilder sb = new StringBuilder();
+ sb.append('<');
+ sb.append(mState.toString());
- if (mPkg != null) {
+ if (mMainPkg != null) {
sb.append(", pkg:"); //$NON-NLS-1$
- sb.append(mPkg.toString());
+ sb.append(mMainPkg.toString());
}
- if (mUpdatePkgs != null && !mUpdatePkgs.isEmpty()) {
+ if (mUpdatePkg != null) {
sb.append(", updated by:"); //$NON-NLS-1$
- sb.append(Arrays.toString(mUpdatePkgs.toArray()));
+ sb.append(mUpdatePkg.toString());
}
+ sb.append('>');
return sb.toString();
}
+
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java
index e858831..0d6dd7a 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java
@@ -16,24 +16,28 @@
package com.android.sdkuilib.internal.repository;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.IPackageVersion;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.Package;
import com.android.sdklib.internal.repository.PlatformPackage;
import com.android.sdklib.internal.repository.PlatformToolPackage;
+import com.android.sdklib.internal.repository.SdkRepoSource;
import com.android.sdklib.internal.repository.SdkSource;
import com.android.sdklib.internal.repository.ToolPackage;
import com.android.sdkuilib.internal.repository.PackageLoader.ISourceLoadedCallback;
import com.android.sdkuilib.internal.repository.PackageLoader.PkgItem;
import com.android.sdkuilib.internal.repository.PackageLoader.PkgState;
+import com.android.sdkuilib.internal.repository.PackagesPage.PackagesDiffLogic.UpdateOp;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.repository.ISdkChangeListener;
import com.android.sdkuilib.ui.GridDataBuilder;
import com.android.sdkuilib.ui.GridLayoutBuilder;
-import com.android.util.Pair;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
@@ -45,6 +49,7 @@ import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeColumnViewerLabelProvider;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
@@ -67,6 +72,7 @@ import org.eclipse.swt.widgets.TreeColumn;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -76,7 +82,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
-import java.util.TreeSet;
import java.util.Map.Entry;
/**
@@ -125,13 +130,7 @@ public class PackagesPage extends UpdaterPage
private final Map<MenuAction, MenuItem> mMenuActions = new HashMap<MenuAction, MenuItem>();
- private final PackageLoader mPackageLoader;
-
- private final List<PkgCategory> mCategories = new ArrayList<PkgCategory>();
- /** Access to this list must be synchronized on {@link #mPackages}. */
- private final List<PkgItem> mPackages = new ArrayList<PkgItem>();
- private final UpdaterData mUpdaterData;
-
+ private final PackagesDiffLogic mDiffLogic;
private boolean mDisplayArchives = false;
private Text mTextSdkOsPath;
@@ -153,21 +152,19 @@ public class PackagesPage extends UpdaterPage
private TreeViewerColumn mColumnStatus;
private Font mTreeFontItalic;
private TreeColumn mTreeColumnName;
- private boolean mLastSortWasByApi;
private boolean mOperationPending;
public PackagesPage(Composite parent, int swtStyle, UpdaterData updaterData) {
super(parent, swtStyle);
- mUpdaterData = updaterData;
- mPackageLoader = new PackageLoader(updaterData);
- createContents(this);
+ mDiffLogic = new PackagesDiffLogic(updaterData);
+ createContents(this);
postCreate(); //$hide$
}
public void onPageSelected() {
- if (mPackages.isEmpty()) {
+ if (mDiffLogic.mCurrentCategories == null || mDiffLogic.mCurrentCategories.isEmpty()) {
// Initialize the package list the first time the page is shown.
loadPackages();
}
@@ -193,6 +190,12 @@ public class PackagesPage extends UpdaterPage
GridLayoutBuilder.create(mGroupPackages).columns(1);
mTreeViewer = new CheckboxTreeViewer(mGroupPackages, SWT.BORDER);
+ mTreeViewer.addFilter(new ViewerFilter() {
+ @Override
+ public boolean select(Viewer viewer, Object parentElement, Object element) {
+ return filterViewerItem(element);
+ }
+ });
mTreeViewer.addCheckStateListener(new ICheckStateListener() {
public void checkStateChanged(CheckStateChangedEvent event) {
@@ -205,7 +208,7 @@ public class PackagesPage extends UpdaterPage
mTree.setHeaderVisible(true);
GridDataBuilder.create(mTree).fill().grab();
- // column name icon is set in sortPackages() depending on the current filter type
+ // column name icon is set when loading depending on the current filter type
// (e.g. API level or source)
mColumnName = new TreeViewerColumn(mTreeViewer, SWT.NONE);
mTreeColumnName = mColumnName.getColumn();
@@ -247,7 +250,7 @@ public class PackagesPage extends UpdaterPage
mCheckFilterNew.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- sortPackages(true /*updateButtons*/);
+ loadPackages();
}
});
mCheckFilterNew.setSelection(true);
@@ -257,7 +260,7 @@ public class PackagesPage extends UpdaterPage
mCheckFilterInstalled.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- sortPackages(true /*updateButtons*/);
+ loadPackages();
}
});
mCheckFilterInstalled.setSelection(true);
@@ -269,7 +272,7 @@ public class PackagesPage extends UpdaterPage
mCheckFilterObsolete.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- sortPackages(true /*updateButtons*/);
+ loadPackages();
}
});
mCheckFilterObsolete.setSelection(false);
@@ -307,9 +310,9 @@ public class PackagesPage extends UpdaterPage
mCheckSortApi.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- sortPackages(true /*updateButtons*/);
+ loadPackages();
// Reset the expanded state when changing sort algorithm
- expandInitial(mCategories);
+ expandInitial(mDiffLogic.mCurrentCategories);
}
});
mCheckSortApi.setText("API level");
@@ -321,9 +324,9 @@ public class PackagesPage extends UpdaterPage
mCheckSortSource.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- sortPackages(true /*updateButtons*/);
+ loadPackages();
// Reset the expanded state when changing sort algorithm
- expandInitial(mCategories);
+ expandInitial(mDiffLogic.mCurrentCategories);
}
});
@@ -354,8 +357,8 @@ public class PackagesPage extends UpdaterPage
}
private Image getImage(String filename) {
- if (mUpdaterData != null) {
- ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ if (mDiffLogic.mUpdaterData != null) {
+ ImageFactory imgFactory = mDiffLogic.mUpdaterData.getImageFactory();
if (imgFactory != null) {
return imgFactory.getImageByName(filename);
}
@@ -379,17 +382,20 @@ public class PackagesPage extends UpdaterPage
switch (action) {
case RELOAD:
+ // Clear all source caches, otherwise loading will use the cached data
+ mDiffLogic.mUpdaterData.getLocalSdkParser().clearPackages();
+ mDiffLogic.mUpdaterData.getSources().clearAllPackages();
loadPackages();
break;
case SHOW_ADDON_SITES:
- AddonSitesDialog d = new AddonSitesDialog(getShell(), mUpdaterData);
+ AddonSitesDialog d = new AddonSitesDialog(getShell(), mDiffLogic.mUpdaterData);
if (d.open()) {
loadPackages();
}
break;
case TOGGLE_SHOW_ARCHIVES:
mDisplayArchives = !mDisplayArchives;
- sortPackages(true /*updateButtons*/);
+ loadPackages();
break;
case TOGGLE_SHOW_INSTALLED_PKG:
button = mCheckFilterInstalled;
@@ -487,8 +493,8 @@ public class PackagesPage extends UpdaterPage
}
private void postCreate() {
- if (mUpdaterData != null) {
- mTextSdkOsPath.setText(mUpdaterData.getOsSdkRoot());
+ if (mDiffLogic.mUpdaterData != null) {
+ mTextSdkOsPath.setText(mDiffLogic.mUpdaterData.getOsSdkRoot());
}
mTreeViewer.setContentProvider(new PkgContentProvider());
@@ -515,55 +521,31 @@ public class PackagesPage extends UpdaterPage
}
private void loadPackages() {
- if (mUpdaterData == null) {
+ if (mDiffLogic.mUpdaterData == null) {
return;
}
- final boolean firstLoad = mPackages.isEmpty();
-
- // Load package is synchronous but does not block the UI.
+ // LoadPackage is synchronous but does not block the UI.
// Consequently it's entirely possible for the user
// to request the app to close whilst the packages are loading. Any
// action done after loadPackages must check the UI hasn't been
// disposed yet. Otherwise hilarity ensues.
- mPackageLoader.loadPackages(new ISourceLoadedCallback() {
- public boolean onSourceLoaded(List<PkgItem> newPkgItems) {
- boolean somethingNew = false;
+ final boolean useSortByApi = isSortByApi();
- synchronized(mPackages) {
- nextNewItem: for (PkgItem newItem : newPkgItems) {
- for (PkgItem existingItem : mPackages) {
- if (existingItem.isSameItemAs(newItem)) {
- // This isn't a new package, we already have it.
- continue nextNewItem;
- }
- }
- mPackages.add(newItem);
- somethingNew = true;
- }
- }
+ if (!mTreeColumnName.isDisposed()) {
+ mTreeColumnName.setImage(
+ getImage(useSortByApi ? ICON_SORT_BY_API : ICON_SORT_BY_SOURCE));
+ }
- if (somethingNew) {
- // Dynamically update the table while we load after each source.
- // Since the official Android source gets loaded first, it makes the
- // window look non-empty a lot sooner.
- if (!mGroupPackages.isDisposed()) {
- mGroupPackages.getDisplay().syncExec(new Runnable() {
- public void run() {
- sortPackages(true /* updateButtons */);
-
- if (!mGroupPackages.isDisposed()) {
- if (firstLoad) {
- // set the initial expanded state
- expandInitial(mCategories);
- }
- updateButtonsState();
- updateMenuCheckmarks();
- }
- }
- });
- }
+ final UpdateOp op = mDiffLogic.updateStart(useSortByApi);
+ mDiffLogic.mPackageLoader.loadPackages(new ISourceLoadedCallback() {
+ boolean needsRefresh = mDiffLogic.isSortByApi() == useSortByApi;
+
+ public boolean onUpdateSource(SdkSource source, Package[] newPackages) {
+ if (mDiffLogic.updateSourcePackages(op, source, newPackages) || needsRefresh) {
+ refreshViewerSync();
+ needsRefresh = false;
}
// Return true to tell the loader to continue with the next source.
@@ -573,287 +555,86 @@ public class PackagesPage extends UpdaterPage
}
public void onLoadCompleted() {
- if (firstLoad && !mGroupPackages.isDisposed()) {
- updateButtonsState();
- updateMenuCheckmarks();
+ if (mDiffLogic.updateEnd(op) || needsRefresh) {
+ refreshViewerSync();
+ needsRefresh = false;
}
}
});
}
- private void sortPackages(boolean updateButtons) {
- if (isSortByApi()) {
- sortByApiLevel();
- } else {
- sortBySource();
- }
- if (updateButtons) {
- updateButtonsState();
- updateMenuCheckmarks();
- }
- }
-
- private boolean isSortByApi() {
- return mCheckSortApi != null && !mCheckSortApi.isDisposed() && mCheckSortApi.getSelection();
- }
-
- /**
- * Recompute the tree by sorting all the packages by API.
- * This does an update in-place of the mCategories list so that the table
- * can preserve its state (checked / expanded / selected) properly.
- */
- private void sortByApiLevel() {
-
- ImageFactory imgFactory = mUpdaterData.getImageFactory();
-
- if (!mTreeColumnName.isDisposed()) {
- mTreeColumnName.setImage(getImage(ICON_SORT_BY_API));
- }
-
- // If the sorting mode changed, clear the categories.
- if (!mLastSortWasByApi) {
- mLastSortWasByApi = true;
- mCategories.clear();
- }
-
- // keep a map of the initial state so that we can detect which items or categories are
- // no longer being used, so that we can removed them at the end of the in-place update.
- final Map<Integer, Pair<PkgApiCategory, HashSet<PkgItem>> > unusedItemsMap =
- new HashMap<Integer, Pair<PkgApiCategory, HashSet<PkgItem>> >();
- final Set<PkgApiCategory> unusedCatSet = new HashSet<PkgApiCategory>();
-
- // get existing categories
- for (PkgCategory cat : mCategories) {
- if (cat instanceof PkgApiCategory) {
- PkgApiCategory acat = (PkgApiCategory) cat;
- unusedCatSet.add(acat);
- unusedItemsMap.put(acat.getKey(),
- Pair.of(acat, new HashSet<PkgItem>(acat.getItems())));
- }
- }
-
- // always add the tools & extras categories, even if empty (unlikely anyway)
- if (!unusedItemsMap.containsKey(PkgApiCategory.KEY_TOOLS)) {
- PkgApiCategory cat = new PkgApiCategory(
- PkgApiCategory.KEY_TOOLS,
- null,
- imgFactory.getImageByName(ICON_CAT_OTHER));
- unusedItemsMap.put(PkgApiCategory.KEY_TOOLS, Pair.of(cat, new HashSet<PkgItem>()));
- mCategories.add(cat);
- }
-
- if (!unusedItemsMap.containsKey(PkgApiCategory.KEY_EXTRA)) {
- PkgApiCategory cat = new PkgApiCategory(
- PkgApiCategory.KEY_EXTRA,
- null,
- imgFactory.getImageByName(ICON_CAT_OTHER));
- unusedItemsMap.put(PkgApiCategory.KEY_EXTRA, Pair.of(cat, new HashSet<PkgItem>()));
- mCategories.add(cat);
- }
-
- synchronized (mPackages) {
- for (PkgItem item : mPackages) {
- if (!keepItem(item)) {
- continue;
- }
-
- int apiKey = item.getApi();
-
- if (apiKey < 1) {
- Package p = item.getPackage();
- if (p instanceof ToolPackage || p instanceof PlatformToolPackage) {
- apiKey = PkgApiCategory.KEY_TOOLS;
- } else {
- apiKey = PkgApiCategory.KEY_EXTRA;
- }
- }
+ private void refreshViewerSync() {
+ // Dynamically update the table while we load after each source.
+ // Since the official Android source gets loaded first, it makes the
+ // window look non-empty a lot sooner.
+ if (!mGroupPackages.isDisposed()) {
+ mGroupPackages.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ if (!mGroupPackages.isDisposed()) {
- Pair<PkgApiCategory, HashSet<PkgItem>> mapEntry = unusedItemsMap.get(apiKey);
-
- if (mapEntry == null) {
- // This is a new category. Create it and add it to the map.
-
- // We need a label for the category.
- // If we have an API level, try to get the info from the SDK Manager.
- // If we don't (e.g. when installing a new platform that isn't yet available
- // locally in the SDK Manager), it's OK we'll try to find the first platform
- // package available.
- String platformName = null;
- if (apiKey != -1) {
- for (IAndroidTarget target : mUpdaterData.getSdkManager().getTargets()) {
- if (target.isPlatform() && target.getVersion().getApiLevel() == apiKey) {
- platformName = target.getVersionName();
- break;
- }
+ if (mTreeViewer.getInput() != mDiffLogic.mCurrentCategories) {
+ // set initial input
+ mTreeViewer.setInput(mDiffLogic.mCurrentCategories);
+ } else {
+ // refresh existing, which preserves the expanded state, the selection
+ // and the checked state.
+ mTreeViewer.refresh();
}
- }
- PkgApiCategory cat = new PkgApiCategory(
- apiKey,
- platformName,
- imgFactory.getImageByName(ICON_CAT_PLATFORM));
- mapEntry = Pair.of(cat, new HashSet<PkgItem>());
- unusedItemsMap.put(apiKey, mapEntry);
- mCategories.add(0, cat);
- }
- PkgApiCategory cat = mapEntry.getFirst();
- assert cat != null;
- unusedCatSet.remove(cat);
-
- HashSet<PkgItem> unusedItemsSet = mapEntry.getSecond();
- unusedItemsSet.remove(item);
- if (!cat.getItems().contains(item)) {
- cat.getItems().add(item);
- }
+ // set the initial expanded state
+ expandInitial(mDiffLogic.mCurrentCategories);
- if (apiKey != -1 && cat.getPlatformName() == null) {
- // Check whether we can get the actual platform version name (e.g. "1.5")
- // from the first Platform package we find in this category.
- Package p = item.getPackage();
- if (p instanceof PlatformPackage) {
- String platformName = ((PlatformPackage) p).getVersionName();
- cat.setPlatformName(platformName);
+ updateButtonsState();
+ updateMenuCheckmarks();
}
}
- }
- }
-
- for (Iterator<PkgCategory> iterCat = mCategories.iterator(); iterCat.hasNext(); ) {
- PkgCategory cat = iterCat.next();
-
- // Remove any unused categories.
- if (unusedCatSet.contains(cat)) {
- iterCat.remove();
- continue;
- }
-
- // Remove any unused items in the category.
- int apikey = cat.getKey();
- Pair<PkgApiCategory, HashSet<PkgItem>> mapEntry = unusedItemsMap.get(apikey);
- if (mapEntry == null) { //DEBUG
- apikey = (apikey + 1) - 1;
- }
- assert mapEntry != null;
- HashSet<PkgItem> unusedItems = mapEntry.getSecond();
- for (Iterator<PkgItem> iterItem = cat.getItems().iterator(); iterItem.hasNext(); ) {
- PkgItem item = iterItem.next();
- if (unusedItems.contains(item)) {
- iterItem.remove();
- }
- }
-
- // Sort the items
- Collections.sort(cat.getItems());
+ });
}
+ }
- // Sort the categories list.
- Collections.sort(mCategories, new Comparator<PkgCategory>() {
- public int compare(PkgCategory cat1, PkgCategory cat2) {
- // We always want categories in order tools..platforms..extras.
- // For platform, we compare in descending order (o2-o1).
- return cat2.getKey() - cat1.getKey();
- }
- });
-
- if (mTreeViewer.getInput() != mCategories) {
- // set initial input
- mTreeViewer.setInput(mCategories);
- } else {
- // refresh existing, which preserves the expanded state, the selection
- // and the checked state.
- mTreeViewer.refresh();
- }
+ private boolean isSortByApi() {
+ return mCheckSortApi != null && !mCheckSortApi.isDisposed() && mCheckSortApi.getSelection();
}
/**
- * Recompute the tree by sorting all packages by source.
+ * Decide whether to keep an item in the current tree based on user-chosen filter options.
*/
- private void sortBySource() {
+ private boolean filterViewerItem(Object treeElement) {
+ if (treeElement instanceof PkgCategory) {
+ PkgCategory cat = (PkgCategory) treeElement;
- if (!mTreeColumnName.isDisposed()) {
- mTreeColumnName.setImage(getImage(ICON_SORT_BY_SOURCE));
- }
-
- mLastSortWasByApi = false;
- mCategories.clear();
-
- Map<SdkSource, List<PkgItem>> sourceMap = new HashMap<SdkSource, List<PkgItem>>();
-
- synchronized(mPackages) {
- for (PkgItem item : mPackages) {
- if (keepItem(item)) {
- SdkSource source = item.getSource();
- List<PkgItem> list = sourceMap.get(source);
- if (list == null) {
- list = new ArrayList<PkgItem>();
- sourceMap.put(source, list);
+ if (!cat.getItems().isEmpty()) {
+ // A category is hidden if all of its content is hidden.
+ // However empty categories are always visible.
+ for (PkgItem item : cat.getItems()) {
+ if (filterViewerItem(item)) {
+ // We found at least one element that is visible.
+ return true;
}
- list.add(item);
}
+ return false;
}
}
- // Sort the sources so that we can create categories sorted the same way
- // (the categories don't link to the sources, so we can't just sort the categories.)
- Set<SdkSource> sources = new TreeSet<SdkSource>(new Comparator<SdkSource>() {
- public int compare(SdkSource o1, SdkSource o2) {
- if (o1 == o2) {
- return 0;
- } else if (o1 == null && o2 != null) {
- return -1;
- } else if (o1 != null && o2 == null) {
- return 1;
- }
- assert o1 != null;
- return o1.toString().compareTo(o2.toString());
- }
- });
- sources.addAll(sourceMap.keySet());
-
- for (SdkSource source : sources) {
- Object key = source != null ? source : "Locally Installed Packages";
- Object iconRef = source != null ? source :
- mUpdaterData.getImageFactory().getImageByName(ICON_PKG_INSTALLED);
+ if (treeElement instanceof PkgItem) {
+ PkgItem item = (PkgItem) treeElement;
- PkgCategory cat = new PkgCategory(
- key.hashCode(),
- key.toString(),
- iconRef);
-
- for (PkgItem item : sourceMap.get(source)) {
- if (item.getSource() == source) {
- cat.getItems().add(item);
+ if (!mCheckFilterObsolete.getSelection()) {
+ if (item.isObsolete()) {
+ return false;
}
}
- mCategories.add(cat);
- }
-
- // We don't support in-place incremental updates so the table gets reset
- // each time we load when sorted by source.
- mTreeViewer.setInput(mCategories);
- }
-
- /**
- * Decide whether to keep an item in the current tree based on user-chosen filter options.
- */
- private boolean keepItem(PkgItem item) {
- if (!mCheckFilterObsolete.getSelection()) {
- if (item.isObsolete()) {
- return false;
- }
- }
-
- if (!mCheckFilterInstalled.getSelection()) {
- if (item.getState() == PkgState.INSTALLED) {
- return false;
+ if (!mCheckFilterInstalled.getSelection()) {
+ if (item.getState() == PkgState.INSTALLED) {
+ return false;
+ }
}
- }
- if (!mCheckFilterNew.getSelection()) {
- if (item.getState() == PkgState.NEW ||
- item.getState() == PkgState.HAS_UPDATE) {
- return false;
+ if (!mCheckFilterNew.getSelection()) {
+ if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
+ return false;
+ }
}
}
@@ -875,8 +656,7 @@ public class PackagesPage extends UpdaterPage
if (pkg instanceof PkgCategory) {
PkgCategory cat = (PkgCategory) pkg;
for (PkgItem item : cat.getItems()) {
- if (item.getState() == PkgState.INSTALLED
- || item.getState() == PkgState.HAS_UPDATE) {
+ if (item.getState() == PkgState.INSTALLED) {
expandInitial(pkg);
break;
}
@@ -986,7 +766,7 @@ public class PackagesPage extends UpdaterPage
break;
}
} else if (c instanceof PkgItem) {
- if (((PkgItem) c).getPackage().hasCompatibleArchive()) {
+ if (((PkgItem) c).getMainPackage().hasCompatibleArchive()) {
canInstall = true;
break;
}
@@ -1004,7 +784,7 @@ public class PackagesPage extends UpdaterPage
for (Object c : checked) {
if (c instanceof PkgItem) {
PkgState state = ((PkgItem) c).getState();
- if (state == PkgState.INSTALLED || state == PkgState.HAS_UPDATE) {
+ if (state == PkgState.INSTALLED) {
canDelete = true;
break;
}
@@ -1017,12 +797,11 @@ public class PackagesPage extends UpdaterPage
private void onSelectNewUpdates() {
ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
- synchronized(mPackages) {
- for (PkgCategory cat : mCategories) {
+ synchronized(mDiffLogic.mCurrentCategories) {
+ for (PkgCategory cat : mDiffLogic.mCurrentCategories) {
boolean selected = false;
for (PkgItem item : cat.getItems()) {
- PkgState state = item.getState();
- if (state == PkgState.NEW || state == PkgState.HAS_UPDATE) {
+ if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
mTreeViewer.setChecked(item, true);
checkExpandItem(item, provider);
selected = true;
@@ -1071,14 +850,14 @@ public class PackagesPage extends UpdaterPage
// This is an update package
p = (Package) c;
} else if (c instanceof PkgItem) {
- p = ((PkgItem) c).getPackage();
+ p = ((PkgItem) c).getMainPackage();
PkgItem pi = (PkgItem) c;
- if (pi.getState() == PkgState.HAS_UPDATE) {
- List<Package> updates = pi.getUpdatePkgs();
+ if (pi.getState() == PkgState.INSTALLED) {
+ Package updPkg = pi.getUpdatePkg();
+ if (updPkg != null) {
// If there's one and only one update, auto-select it instead.
- if (updates != null && updates.size() == 1) {
- p = updates.get(0);
+ p = updPkg;
}
}
}
@@ -1093,33 +872,18 @@ public class PackagesPage extends UpdaterPage
}
}
- if (mUpdaterData != null) {
+ if (mDiffLogic.mUpdaterData != null) {
try {
beginOperationPending();
- mUpdaterData.updateOrInstallAll_WithGUI(
+ mDiffLogic.mUpdaterData.updateOrInstallAll_WithGUI(
archives,
mCheckFilterObsolete.getSelection() /* includeObsoletes */);
} finally {
endOperationPending();
- // Remove any pkg item matching anything we potentially installed
- // then request the package list to be updated. This will prevent
- // from having stale entries.
- synchronized(mPackages) {
- for (Archive a : archives) {
- for (Iterator<PkgItem> it = mPackages.iterator(); it.hasNext(); ) {
- PkgItem pi = it.next();
- if (pi.hasArchive(a)) {
- it.remove();
- break;
- }
- }
- }
- }
-
// The local package list has changed, make sure to refresh it
- mUpdaterData.getLocalSdkParser().clearPackages();
+ mDiffLogic.mUpdaterData.getLocalSdkParser().clearPackages();
loadPackages();
}
}
@@ -1143,8 +907,8 @@ public class PackagesPage extends UpdaterPage
if (c instanceof PkgItem) {
PkgItem pi = (PkgItem) c;
PkgState state = pi.getState();
- if (state == PkgState.INSTALLED || state == PkgState.HAS_UPDATE) {
- Package p = pi.getPackage();
+ if (state == PkgState.INSTALLED) {
+ Package p = pi.getMainPackage();
Archive[] as = p.getArchives();
if (as.length == 1 && as[0] != null && as[0].isLocal()) {
@@ -1167,7 +931,7 @@ public class PackagesPage extends UpdaterPage
try {
beginOperationPending();
- mUpdaterData.getTaskFactory().start("Delete Package", new ITask() {
+ mDiffLogic.mUpdaterData.getTaskFactory().start("Delete Package", new ITask() {
public void run(ITaskMonitor monitor) {
monitor.setProgressMax(archives.size() + 1);
for (Entry<Archive, PkgItem> entry : archives.entrySet()) {
@@ -1177,13 +941,9 @@ public class PackagesPage extends UpdaterPage
a.getParentPackage().getShortDescription(),
a.getLocalOsPath());
- // Delete the actual package and its internal representation
+ // Delete the actual package
a.deleteLocal();
- synchronized(mPackages) {
- mPackages.remove(entry.getValue());
- }
-
monitor.incProgress(1);
if (monitor.isCancelRequested()) {
break;
@@ -1198,7 +958,7 @@ public class PackagesPage extends UpdaterPage
endOperationPending();
// The local package list has changed, make sure to refresh it
- mUpdaterData.getLocalSdkParser().clearPackages();
+ mDiffLogic.mUpdaterData.getLocalSdkParser().clearPackages();
loadPackages();
}
}
@@ -1224,7 +984,7 @@ public class PackagesPage extends UpdaterPage
if (element instanceof PkgCategory) {
return ((PkgCategory) element).getLabel();
} else if (element instanceof PkgItem) {
- return getPkgItemname((PkgItem) element);
+ return getPkgItemName((PkgItem) element);
} else if (element instanceof IDescription) {
return ((IDescription) element).getShortDescription();
}
@@ -1244,8 +1004,7 @@ public class PackagesPage extends UpdaterPage
if (element instanceof PkgItem) {
PkgItem pkg = (PkgItem) element;
- if (pkg.getState() == PkgState.INSTALLED ||
- pkg.getState() == PkgState.HAS_UPDATE) {
+ if (pkg.getState() == PkgState.INSTALLED) {
return Integer.toString(pkg.getRevision());
}
}
@@ -1257,19 +1016,16 @@ public class PackagesPage extends UpdaterPage
switch(pkg.getState()) {
case INSTALLED:
- return "Installed";
- case HAS_UPDATE:
- List<Package> updates = pkg.getUpdatePkgs();
- if (updates.size() == 1) {
- return String.format("Update available: rev. %1$s",
- updates.get(0).getRevision());
- } else {
- // This case should not happen in *our* typical release workflow.
- return "Multiple updates available";
+ Package update = pkg.getUpdatePkg();
+ if (update != null) {
+ return String.format(
+ "Update available: rev. %1$s",
+ update.getRevision());
}
+ return "Installed";
+
case NEW:
return "Not installed";
- // TODO display pkg.getRevision() in a tooltip
}
return pkg.getState().toString();
@@ -1279,10 +1035,10 @@ public class PackagesPage extends UpdaterPage
}
}
- return ""; //$NON-NLS-1$
+ return "";
}
- private String getPkgItemname(PkgItem item) {
+ private String getPkgItemName(PkgItem item) {
String name = item.getName().trim();
if (isSortByApi()) {
@@ -1299,14 +1055,23 @@ public class PackagesPage extends UpdaterPage
} else if (apiLabel != null && name.endsWith(apiLabel)) {
return name.substring(0, name.length() - apiLabel.length());
+
+ } else if (platLabel != null && item.isObsolete() && name.indexOf(platLabel) > 0) {
+ // For obsolete items, the format is "<base name> <platform name> (Obsolete)"
+ // so in this case only accept removing a platform name that is not at
+ // the end.
+ name = name.replace(platLabel, ""); //$NON-NLS-1$
}
}
+ // Collapse potential duplicated spacing
+ name = name.replaceAll(" +", " "); //$NON-NLS-1$ //$NON-NLS-2$
+
return name;
}
private PkgCategory findCategoryForItem(PkgItem item) {
- for (PkgCategory cat : mCategories) {
+ for (PkgCategory cat : mDiffLogic.mCurrentCategories) {
for (PkgItem i : cat.getItems()) {
if (i == item) {
return cat;
@@ -1319,23 +1084,26 @@ public class PackagesPage extends UpdaterPage
@Override
public Image getImage(Object element) {
- ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ ImageFactory imgFactory = mDiffLogic.mUpdaterData.getImageFactory();
if (imgFactory != null) {
if (mColumn == mColumnName) {
if (element instanceof PkgCategory) {
return imgFactory.getImageForObject(((PkgCategory) element).getIconRef());
} else if (element instanceof PkgItem) {
- return imgFactory.getImageForObject(((PkgItem) element).getPackage());
+ return imgFactory.getImageForObject(((PkgItem) element).getMainPackage());
}
return imgFactory.getImageForObject(element);
} else if (mColumn == mColumnStatus && element instanceof PkgItem) {
- switch(((PkgItem) element).getState()) {
+ PkgItem pi = (PkgItem) element;
+ switch(pi.getState()) {
case INSTALLED:
- return imgFactory.getImageByName(ICON_PKG_INSTALLED);
- case HAS_UPDATE:
- return imgFactory.getImageByName(ICON_PKG_UPDATE);
+ if (pi.hasUpdatePkg()) {
+ return imgFactory.getImageByName(ICON_PKG_UPDATE);
+ } else {
+ return imgFactory.getImageByName(ICON_PKG_INSTALLED);
+ }
case NEW:
return imgFactory.getImageByName(ICON_PKG_NEW);
}
@@ -1370,15 +1138,15 @@ public class PackagesPage extends UpdaterPage
return ((PkgCategory) parentElement).getItems().toArray();
} else if (parentElement instanceof PkgItem) {
- List<Package> pkgs = ((PkgItem) parentElement).getUpdatePkgs();
+ if (mDisplayArchives) {
- // Display update packages as sub-items if there's more than one
- // or if the archive/details mode is activated.
- if (pkgs != null && (pkgs.size() > 1 || mDisplayArchives)) {
- return pkgs.toArray();
- }
+ Package pkg = ((PkgItem) parentElement).getUpdatePkg();
+
+ // Display update packages as sub-items if the details mode is activated.
+ if (pkg != null) {
+ return new Object[] { pkg };
+ }
- if (mDisplayArchives) {
return ((PkgItem) parentElement).getArchives();
}
@@ -1406,15 +1174,14 @@ public class PackagesPage extends UpdaterPage
return true;
} else if (parentElement instanceof PkgItem) {
- List<Package> pkgs = ((PkgItem) parentElement).getUpdatePkgs();
+ if (mDisplayArchives) {
+ Package pkg = ((PkgItem) parentElement).getUpdatePkg();
- // Display update packages as sub-items if there's more than one
- // or if the archive/details mode is activated.
- if (pkgs != null && (pkgs.size() > 1 || mDisplayArchives)) {
- return !pkgs.isEmpty();
- }
+ // Display update packages as sub-items if the details mode is activated.
+ if (pkg != null) {
+ return true;
+ }
- if (mDisplayArchives) {
Archive[] archives = ((PkgItem) parentElement).getArchives();
return archives.length > 0;
}
@@ -1441,23 +1208,22 @@ public class PackagesPage extends UpdaterPage
}
}
- private static class PkgCategory {
- private final int mKey;
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ static abstract class PkgCategory {
+ private final Object mKey;
private final Object mIconRef;
private final List<PkgItem> mItems = new ArrayList<PkgItem>();
private String mLabel;
+ /** Transient flag used during incremental updates. */
+ private boolean mUnused;
- // When sorting by Source, key is the hash of the source's name.
- // When storing by API, key is the API level (>=1). Tools and extra have the
- // special values.
-
- public PkgCategory(int key, String label, Object iconRef) {
+ public PkgCategory(Object key, String label, Object iconRef) {
mKey = key;
mLabel = label;
mIconRef = iconRef;
}
- public int getKey() {
+ public Object getKey() {
return mKey;
}
@@ -1476,6 +1242,45 @@ public class PackagesPage extends UpdaterPage
public List<PkgItem> getItems() {
return mItems;
}
+
+ public void setUnused(boolean unused) {
+ mUnused = unused;
+ }
+
+ public boolean isUnused() {
+ return mUnused;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s <key=%08x, label=%s, #items=%d>",
+ this.getClass().getSimpleName(),
+ mKey == null ? "null" : mKey.toString(),
+ mLabel,
+ mItems.size());
+ }
+
+ /** {@link PkgCategory}s are equal if their internal keys are equal. */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mKey == null) ? 0 : mKey.hashCode());
+ return result;
+ }
+
+ /** {@link PkgCategory}s are equal if their internal keys are equal. */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ PkgCategory other = (PkgCategory) obj;
+ if (mKey == null) {
+ if (other.mKey != null) return false;
+ } else if (!mKey.equals(other.mKey)) return false;
+ return true;
+ }
}
private static class PkgApiCategory extends PkgCategory {
@@ -1509,18 +1314,21 @@ public class PackagesPage extends UpdaterPage
}
public String getApiLabel() {
- int api = getKey();
- if (api > 0) {
+ int api = ((Integer) getKey()).intValue();
+ if (api == KEY_TOOLS) {
+ return "TOOLS"; //$NON-NLS-1$ for internal use only
+ } else if (api == KEY_EXTRA) {
+ return "EXTRAS"; //$NON-NLS-1$ for internal use only
+ } else {
return String.format("API %1$d", getKey());
}
- return null;
}
@Override
public String getLabel() {
String label = super.getLabel();
if (label == null) {
- int key = getKey();
+ int key = ((Integer) getKey()).intValue();
if (key == KEY_TOOLS) {
label = "Tools";
@@ -1540,10 +1348,51 @@ public class PackagesPage extends UpdaterPage
@Override
public void setLabel(String label) {
- throw new UnsupportedOperationException("Use setPlatformName() instead."); //$NON-NLS-1$
+ throw new UnsupportedOperationException("Use setPlatformName() instead.");
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s <API=%s, label=%s, #items=%d>",
+ this.getClass().getSimpleName(),
+ getApiLabel(),
+ getLabel(),
+ getItems().size());
}
}
+ private static class PkgSourceCategory extends PkgCategory {
+
+ /**
+ * A special {@link SdkSource} object that represents the locally installed
+ * items, or more exactly a lack of remote source.
+ */
+ public final static SdkSource UNKNOWN_SOURCE =
+ new SdkRepoSource("http://no.source", "Local Packages");
+ private final SdkSource mSource;
+
+ public PkgSourceCategory(SdkSource source, UpdaterData updaterData) {
+ super(
+ source, // the source is the key and it can be null
+ source == UNKNOWN_SOURCE ? "Local Packages" : source.toString(),
+ source == UNKNOWN_SOURCE ?
+ updaterData.getImageFactory().getImageByName(ICON_PKG_INSTALLED) :
+ source);
+ mSource = source;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s <source=%s, #items=%d>",
+ this.getClass().getSimpleName(),
+ mSource.toString(),
+ getItems().size());
+ }
+
+ public SdkSource getSource() {
+ return mSource;
+ }
+ }
// --- Implementation of ISdkChangeListener ---
@@ -1563,6 +1412,521 @@ public class PackagesPage extends UpdaterPage
// nothing to be done for now.
}
+
+ /**
+ * Helper class that separates the logic of package management from the UI
+ * so that we can test it using head-less unit tests.
+ */
+ static class PackagesDiffLogic {
+ final PackageLoader mPackageLoader;
+ final UpdaterData mUpdaterData;
+
+ final List<PkgCategory> mApiCategories = new ArrayList<PkgCategory>();
+ final List<PkgCategory> mSourceCategories = new ArrayList<PkgCategory>();
+ List<PkgCategory> mCurrentCategories = mApiCategories;
+
+ public PackagesDiffLogic(UpdaterData updaterData) {
+ mUpdaterData = updaterData;
+ mPackageLoader = new PackageLoader(updaterData);
+ }
+
+ /**
+ * An update operation, customized to either sort by API or sort by source.
+ */
+ abstract class UpdateOp {
+ public final Set<SdkSource> mVisitedSources = new HashSet<SdkSource>();
+
+ /** Retrieve the category key for the given package, either local or remote. */
+ public abstract Object getCategoryKey(Package pkg);
+ /** Modified {@code currentCategories} to add default categories. */
+ public abstract void addDefaultCategories(List<PkgCategory> currentCategories);
+ /** Creates the category for the given key and returns it. */
+ public abstract PkgCategory createCategory(Object catKey);
+ /** Sorts the category list (but not the items within the categories.) */
+ public abstract void sortCategoryList(List<PkgCategory> categoryList);
+ /** Called after items of a given category have changed. Used to sort the
+ * items and/or adjust the category name. */
+ public abstract void postCategoryItemsChanged(List<PkgCategory> categoryList);
+ /** Add the new package or merge it as an update or does nothing if this package
+ * is already part of the category items.
+ * Returns true if the category item list has changed. */
+ public abstract boolean mergeNewPackage(Package newPackage, PkgCategory cat);
+ }
+
+ public boolean isSortByApi() {
+ return mCurrentCategories == mApiCategories;
+ }
+
+ public UpdateOp updateStart(boolean sortByApi) {
+ mCurrentCategories = sortByApi ? mApiCategories : mSourceCategories;
+
+ UpdateOp info = sortByApi ? (new UpdateOpApi()) : (new UpdateOpSource());
+
+ // Note that default categories are created after the unused ones so that
+ // the callback can decide whether they should be marked as unused or not.
+ for (PkgCategory cat : mCurrentCategories) {
+ cat.setUnused(true);
+ }
+
+ info.addDefaultCategories(mCurrentCategories);
+
+ return info;
+ }
+
+ public boolean updateSourcePackages(UpdateOp op, SdkSource source, Package[] newPackages) {
+ if (newPackages.length > 0) {
+ op.mVisitedSources.add(source);
+ }
+ if (source == null) {
+ return processLocals(op, newPackages);
+ } else {
+ return processSource(op, source, newPackages);
+ }
+ }
+
+ public boolean updateEnd(UpdateOp op) {
+ boolean hasChanged = false;
+
+ // Remove unused categories
+ for (Iterator<PkgCategory> catIt = mCurrentCategories.iterator(); catIt.hasNext(); ) {
+ PkgCategory cat = catIt.next();
+ if (cat.isUnused()) {
+ catIt.remove();
+ hasChanged = true;
+ continue;
+ }
+
+ // Remove all items which source we have not been visited. They are obsolete.
+ for (Iterator<PkgItem> itemIt = cat.getItems().iterator(); itemIt.hasNext(); ) {
+ PkgItem item = itemIt.next();
+ if (!op.mVisitedSources.contains(item.getSource())) {
+ itemIt.remove();
+ hasChanged = true;
+ }
+ }
+ }
+ return hasChanged;
+ }
+
+ /** Process all local packages. Returns true if something changed.
+ * @param op */
+ private boolean processLocals(UpdateOp op, Package[] packages) {
+ boolean hasChanged = false;
+ Set<Package> newPackages = new HashSet<Package>(Arrays.asList(packages));
+ Set<Package> unusedPackages = new HashSet<Package>(newPackages);
+
+ assert newPackages.size() == packages.length;
+
+ // Upgrade 'new' items to 'installed' for any local package we already know about
+ for (PkgCategory cat : mCurrentCategories) {
+ List<PkgItem> items = cat.getItems();
+ for (int i = 0; i < items.size(); i++) {
+ PkgItem item = items.get(i);
+
+ if (item.hasUpdatePkg() && newPackages.contains(item.getUpdatePkg())) {
+ // This item has an update package that is now installed.
+ PkgItem installed = new PkgItem(item.getUpdatePkg(), PkgState.INSTALLED);
+ unusedPackages.remove(item.getUpdatePkg());
+ item.removeUpdate();
+ items.add(installed);
+ cat.setUnused(false);
+ hasChanged = true;
+ }
+
+ if (newPackages.contains(item.getMainPackage())) {
+ unusedPackages.remove(item.getMainPackage());
+ if (item.getState() == PkgState.NEW) {
+ // This item has a main package that is now installed.
+ item.setState(PkgState.INSTALLED);
+ cat.setUnused(false);
+ hasChanged = true;
+ }
+ }
+ }
+ }
+
+ // Downgrade 'installed' items to 'new' if their package isn't listed anymore
+ for (PkgCategory cat : mCurrentCategories) {
+ for (PkgItem item : cat.getItems()) {
+ if (item.getState() == PkgState.INSTALLED &&
+ !newPackages.contains(item.getMainPackage())) {
+ item.setState(PkgState.NEW);
+ hasChanged = true;
+ }
+ }
+ }
+
+ // Create new 'installed' items for any local package we haven't processed yet
+ for (Package newPackage : unusedPackages) {
+ Object catKey = op.getCategoryKey(newPackage);
+ PkgCategory cat = findCurrentCategory(mCurrentCategories, catKey);
+
+ if (cat == null) {
+ // This is a new category. Create it and add it to the list.
+ cat = op.createCategory(catKey);
+ mCurrentCategories.add(cat);
+ op.sortCategoryList(mCurrentCategories);
+ }
+
+ cat.getItems().add(new PkgItem(newPackage, PkgState.INSTALLED));
+ cat.setUnused(false);
+ hasChanged = true;
+ }
+
+ if (hasChanged) {
+ op.postCategoryItemsChanged(mCurrentCategories);
+ }
+
+ return hasChanged;
+ }
+
+ /** Process all remote packages. Returns true if something changed.
+ * @param op */
+ private boolean processSource(UpdateOp op, SdkSource source, Package[] packages) {
+ boolean hasChanged = false;
+ // Note: unusedPackages must respect the original packages order. It can't be a set.
+ List<Package> unusedPackages = new ArrayList<Package>(Arrays.asList(packages));
+ Set<Package> newPackages = new HashSet<Package>(unusedPackages);
+
+ assert newPackages.size() == packages.length;
+
+ // Remove any items or updates that are no longer in the source's packages
+ for (PkgCategory cat : mCurrentCategories) {
+ List<PkgItem> items = cat.getItems();
+ for (int i = 0; i < items.size(); i++) {
+ PkgItem item = items.get(i);
+ SdkSource itemSource = item.getSource();
+
+ // Only process items matching the current source
+ if (!(itemSource == source || (source != null && source.equals(itemSource)))) {
+ continue;
+ }
+ // Installed items have been dealt with the local source,
+ // so only change new items here
+ if (item.getState() == PkgState.NEW &&
+ !newPackages.contains(item.getMainPackage())) {
+ // This package is no longer part of the source.
+ items.remove(i--);
+ hasChanged = true;
+ continue;
+ }
+
+ cat.setUnused(false);
+ unusedPackages.remove(item.getMainPackage());
+
+ if (item.hasUpdatePkg()) {
+ if (newPackages.contains(item.getUpdatePkg())) {
+ unusedPackages.remove(item.getUpdatePkg());
+ } else {
+ // This update is no longer part of the source
+ item.removeUpdate();
+ hasChanged = true;
+ }
+ }
+ }
+ }
+
+ // Add any new unknown packages
+ for (Package newPackage : unusedPackages) {
+ Object catKey = op.getCategoryKey(newPackage);
+ PkgCategory cat = findCurrentCategory(mCurrentCategories, catKey);
+
+ if (cat == null) {
+ // This is a new category. Create it and add it to the list.
+ cat = op.createCategory(catKey);
+ mCurrentCategories.add(cat);
+ op.sortCategoryList(mCurrentCategories);
+ }
+
+ // Add the new package or merge it as an update
+ hasChanged |= op.mergeNewPackage(newPackage, cat);
+ }
+
+ if (hasChanged) {
+ op.postCategoryItemsChanged(mCurrentCategories);
+ }
+
+ return hasChanged;
+ }
+
+ private PkgCategory findCurrentCategory(
+ List<PkgCategory> currentCategories,
+ Object categoryKey) {
+ for (PkgCategory cat : currentCategories) {
+ if (cat.getKey().equals(categoryKey)) {
+ return cat;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * {@link UpdateOp} describing the Sort-by-API operation.
+ */
+ private class UpdateOpApi extends UpdateOp {
+ @Override
+ public Object getCategoryKey(Package pkg) {
+ // Sort by API
+
+ if (pkg instanceof IPackageVersion) {
+ return ((IPackageVersion) pkg).getVersion().getApiLevel();
+
+ } else if (pkg instanceof ToolPackage || pkg instanceof PlatformToolPackage) {
+ return PkgApiCategory.KEY_TOOLS;
+
+ } else {
+ return PkgApiCategory.KEY_EXTRA;
+ }
+ }
+
+ @Override
+ public void addDefaultCategories(List<PkgCategory> currentCategories) {
+ boolean needTools = true;
+ boolean needExtras = true;
+
+ for (PkgCategory cat : currentCategories) {
+ if (cat.getKey().equals(PkgApiCategory.KEY_TOOLS)) {
+ // Mark them as no unused to prevent their removal in updateEnd().
+ cat.setUnused(false);
+ needTools = false;
+ } else if (cat.getKey().equals(PkgApiCategory.KEY_EXTRA)) {
+ cat.setUnused(false);
+ needExtras = false;
+ }
+ }
+
+ // Always add the tools & extras categories, even if empty (unlikely anyway)
+ if (needTools) {
+ PkgApiCategory acat = new PkgApiCategory(
+ PkgApiCategory.KEY_TOOLS,
+ null,
+ mUpdaterData.getImageFactory().getImageByName(ICON_CAT_OTHER));
+ currentCategories.add(acat);
+ }
+
+ if (needExtras) {
+ PkgApiCategory acat = new PkgApiCategory(
+ PkgApiCategory.KEY_EXTRA,
+ null,
+ mUpdaterData.getImageFactory().getImageByName(ICON_CAT_OTHER));
+ currentCategories.add(acat);
+ }
+ }
+
+ @Override
+ public PkgCategory createCategory(Object catKey) {
+ // Create API category.
+ PkgCategory cat = null;
+
+ assert catKey instanceof Integer;
+ int apiKey = ((Integer) catKey).intValue();
+
+ // We need a label for the category.
+ // If we have an API level, try to get the info from the SDK Manager.
+ // If we don't (e.g. when installing a new platform that isn't yet available
+ // locally in the SDK Manager), it's OK we'll try to find the first platform
+ // package available.
+ String platformName = null;
+ if (apiKey >= 1 && apiKey != PkgApiCategory.KEY_TOOLS) {
+ for (IAndroidTarget target :
+ mUpdaterData.getSdkManager().getTargets()) {
+ if (target.isPlatform() &&
+ target.getVersion().getApiLevel() == apiKey) {
+ platformName = target.getVersionName();
+ break;
+ }
+ }
+ }
+
+ cat = new PkgApiCategory(
+ apiKey,
+ platformName,
+ mUpdaterData.getImageFactory().getImageByName(ICON_CAT_PLATFORM));
+
+ return cat;
+ }
+
+ @Override
+ public boolean mergeNewPackage(Package newPackage, PkgCategory cat) {
+ // First check if the new package could be an update
+ // to an existing package
+ for (PkgItem item : cat.getItems()) {
+ if (item.isSameMainPackageAs(newPackage)) {
+ // Seems like this isn't really a new item after all.
+ cat.setUnused(false);
+ // Return false since we're not changing anything.
+ return false;
+ } else if (item.mergeUpdate(newPackage)) {
+ // The new package is an update for the existing package
+ // and has been merged in the PkgItem as such.
+ cat.setUnused(false);
+ // Return true to indicate we changed something.
+ return true;
+ }
+ }
+
+ // This is truly a new item.
+ cat.getItems().add(new PkgItem(newPackage, PkgState.NEW));
+ cat.setUnused(false);
+ return true; // something has changed
+ }
+
+ @Override
+ public void sortCategoryList(List<PkgCategory> categoryList) {
+ // Sort the categories list.
+ // We always want categories in order tools..platforms..extras.
+ // For platform, we compare in descending order (o2-o1).
+ // This order is achieved by having the category keys ordered as
+ // needed for the sort to just do what we expect.
+
+ Collections.sort(categoryList, new Comparator<PkgCategory>() {
+ public int compare(PkgCategory cat1, PkgCategory cat2) {
+ assert cat1 instanceof PkgApiCategory;
+ assert cat2 instanceof PkgApiCategory;
+ int api1 = ((Integer) cat1.getKey()).intValue();
+ int api2 = ((Integer) cat2.getKey()).intValue();
+ return api2 - api1;
+ }
+ });
+ }
+
+ @Override
+ public void postCategoryItemsChanged(List<PkgCategory> categoryList) {
+ // Sort the items
+ for (PkgCategory cat : mCurrentCategories) {
+ Collections.sort(cat.getItems());
+
+ // When sorting by API, we can't always get the platform name
+ // from the package manager. In this case at the very end we
+ // look for a potential platform package we can use to extract
+ // the platform version name (e.g. '1.5') from the first suitable
+ // platform package we can find.
+
+ assert cat instanceof PkgApiCategory;
+ PkgApiCategory pac = (PkgApiCategory) cat;
+ if (pac.getPlatformName() == null) {
+ // Check whether we can get the actual platform version name (e.g. "1.5")
+ // from the first Platform package we find in this category.
+
+ for (PkgItem item : cat.getItems()) {
+ Package p = item.getMainPackage();
+ if (p instanceof PlatformPackage) {
+ String platformName = ((PlatformPackage) p).getVersionName();
+ if (platformName != null) {
+ pac.setPlatformName(platformName);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * {@link UpdateOp} describing the Sort-by-Source operation.
+ */
+ private class UpdateOpSource extends UpdateOp {
+ @Override
+ public Object getCategoryKey(Package pkg) {
+ // Sort by source
+ SdkSource source = pkg.getParentSource();
+ if (source == null) {
+ return PkgSourceCategory.UNKNOWN_SOURCE;
+ }
+ return source;
+ }
+
+ @Override
+ public void addDefaultCategories(List<PkgCategory> currentCategories) {
+ for (PkgCategory cat : currentCategories) {
+ if (cat.getKey().equals(PkgSourceCategory.UNKNOWN_SOURCE)) {
+ // Already present.
+ return;
+ }
+ }
+
+ // Always add the local categories, even if empty (unlikely anyway)
+ PkgSourceCategory cat = new PkgSourceCategory(
+ PkgSourceCategory.UNKNOWN_SOURCE,
+ mUpdaterData);
+ // Mark it as unused so that it can be cleared in updateEnd() if not used.
+ cat.setUnused(true);
+ currentCategories.add(cat);
+ }
+
+ @Override
+ public PkgCategory createCategory(Object catKey) {
+ assert catKey instanceof SdkSource;
+ PkgCategory cat = new PkgSourceCategory((SdkSource) catKey, mUpdaterData);
+ return cat;
+
+ }
+
+ @Override
+ public boolean mergeNewPackage(Package newPackage, PkgCategory cat) {
+ // First check if the new package could be an update
+ // to an existing package
+ for (PkgItem item : cat.getItems()) {
+ if (item.isSameMainPackageAs(newPackage)) {
+ // Seems like this isn't really a new item after all.
+ cat.setUnused(false);
+ // Return false since we're not changing anything.
+ return false;
+ } else if (item.mergeUpdate(newPackage)) {
+ // The new package is an update for the existing package
+ // and has been merged in the PkgItem as such.
+ cat.setUnused(false);
+ // Return true to indicate we changed something.
+ return true;
+ }
+ }
+
+ // This is truly a new item.
+ cat.getItems().add(new PkgItem(newPackage, PkgState.NEW));
+ cat.setUnused(false);
+ return true; // something has changed
+ }
+
+ @Override
+ public void sortCategoryList(List<PkgCategory> categoryList) {
+ // Sort the sources in ascending source name order,
+ // with the local packages always first.
+
+ Collections.sort(categoryList, new Comparator<PkgCategory>() {
+ public int compare(PkgCategory cat1, PkgCategory cat2) {
+ assert cat1 instanceof PkgSourceCategory;
+ assert cat2 instanceof PkgSourceCategory;
+
+ SdkSource src1 = ((PkgSourceCategory) cat1).getSource();
+ SdkSource src2 = ((PkgSourceCategory) cat2).getSource();
+
+ if (src1 == src2) {
+ return 0;
+ } else if (src1 == PkgSourceCategory.UNKNOWN_SOURCE) {
+ return -1;
+ } else if (src2 == PkgSourceCategory.UNKNOWN_SOURCE) {
+ return 1;
+ }
+ assert src1 != null; // true because LOCAL_SOURCE==null
+ assert src2 != null;
+ return src1.toString().compareTo(src2.toString());
+ }
+ });
+ }
+
+ @Override
+ public void postCategoryItemsChanged(List<PkgCategory> categoryList) {
+ // Sort the items
+ for (PkgCategory cat : mCurrentCategories) {
+ Collections.sort(cat.getItems());
+ }
+ }
+ }
+ }
+
+
// --- End of hiding from SWT Designer ---
//$hide<<$
}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java
new file mode 100755
index 0000000..964c30a
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011 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.repository;
+
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.Package;
+import com.android.sdklib.internal.repository.SdkSource;
+import com.android.sdklib.internal.repository.Archive.Arch;
+import com.android.sdklib.internal.repository.Archive.Os;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * A mock empty package, of no particular subpackage type.
+ * {@link #sameItemAs(Package)} will return true if these packages have the same handle.
+ */
+class MockEmptyPackage extends Package {
+ private final String mTestHandle;
+
+ /**
+ * Create a new {@link MockEmptyPackage}.
+ * @param testHandle The comparison handle for {@link #sameItemAs(Package)}.
+ */
+ public MockEmptyPackage(String testHandle) {
+ super(
+ null /*source*/,
+ null /*props*/,
+ 0 /*revision*/,
+ null /*license*/,
+ null /*description*/,
+ null /*descUrl*/,
+ Os.ANY /*archiveOs*/,
+ Arch.ANY /*archiveArch*/,
+ null /*archiveOsPath*/
+ );
+ mTestHandle = testHandle;
+ }
+
+ /**
+ * Create a new {@link MockEmptyPackage}.
+ * @param testHandle The comparison handle for {@link #sameItemAs(Package)}.
+ * @param revision The revision of the package, printed in the short description.
+ */
+ public MockEmptyPackage(String testHandle, int revision) {
+ super(
+ null /*source*/,
+ null /*props*/,
+ revision,
+ null /*license*/,
+ null /*description*/,
+ null /*descUrl*/,
+ Os.ANY /*archiveOs*/,
+ Arch.ANY /*archiveArch*/,
+ null /*archiveOsPath*/
+ );
+ mTestHandle = testHandle;
+ }
+
+ /**
+ * Create a new {@link MockEmptyPackage}.
+ * @param source The source associate with this package.
+ * @param testHandle The comparison handle for {@link #sameItemAs(Package)}.
+ * @param revision The revision of the package, printed in the short description.
+ */
+ public MockEmptyPackage(SdkSource source, String testHandle, int revision) {
+ super(
+ source,
+ null /*props*/,
+ revision,
+ null /*license*/,
+ null /*description*/,
+ null /*descUrl*/,
+ Os.ANY /*archiveOs*/,
+ Arch.ANY /*archiveArch*/,
+ null /*archiveOsPath*/
+ );
+ mTestHandle = testHandle;
+ }
+
+ @Override
+ protected Archive createLocalArchive(
+ Properties props,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ return new Archive(this, props, archiveOs, archiveArch, archiveOsPath) {
+ @Override
+ public String toString() {
+ return mTestHandle;
+ }
+ };
+ }
+
+ public Archive getLocalArchive() {
+ return getArchives()[0];
+ }
+
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ return null;
+ }
+
+ @Override
+ public String getListDescription() {
+ return this.getClass().getSimpleName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
+ sb.append(" '").append(mTestHandle).append('\'');
+ if (getRevision() > 0) {
+ sb.append(" rev=").append(getRevision());
+ }
+ return sb.toString();
+ }
+
+ /** Returns true if these packages have the same handle. */
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ return (pkg instanceof MockEmptyPackage) &&
+ mTestHandle.equals(((MockEmptyPackage) pkg).mTestHandle);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mTestHandle == null) ? 0 : mTestHandle.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof MockEmptyPackage)) {
+ return false;
+ }
+ MockEmptyPackage other = (MockEmptyPackage) obj;
+ if (mTestHandle == null) {
+ if (other.mTestHandle != null) {
+ return false;
+ }
+ } else if (!mTestHandle.equals(other.mTestHandle)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java
new file mode 100755
index 0000000..20cd48a
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2011 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.repository;
+
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.ArchiveInstaller;
+import com.android.sdklib.internal.repository.ITask;
+import com.android.sdklib.internal.repository.ITaskFactory;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.MockEmptySdkManager;
+import com.android.sdklib.mock.MockLog;
+import com.android.sdkuilib.internal.repository.icons.ImageFactory;
+
+import org.eclipse.swt.graphics.Image;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A mock UpdaterData that simply records what would have been installed. */
+class MockUpdaterData extends UpdaterData {
+
+ public final static String SDK_PATH = "/tmp/SDK";
+
+ private final List<Archive> mInstalled = new ArrayList<Archive>();
+
+ public MockUpdaterData() {
+ super(SDK_PATH, new MockLog());
+
+ setTaskFactory(new MockTaskFactory());
+ setImageFactory(new NullImageFactory());
+ }
+
+ /** Gives access to the internal {@link #installArchives(List)}. */
+ public void _installArchives(List<ArchiveInfo> result) {
+ installArchives(result);
+ }
+
+ public Archive[] getInstalled() {
+ return mInstalled.toArray(new Archive[mInstalled.size()]);
+ }
+
+ @Override
+ protected void initSdk() {
+ setSdkManager(new MockEmptySdkManager(SDK_PATH));
+ }
+
+ @Override
+ public void reloadSdk() {
+ // bypass original implementation
+ }
+
+ /** Returns a mock installer that simply records what would have been installed. */
+ @Override
+ protected ArchiveInstaller createArchiveInstaler() {
+ return new ArchiveInstaller() {
+ @Override
+ public boolean install(
+ Archive archive,
+ String osSdkRoot,
+ boolean forceHttp,
+ SdkManager sdkManager,
+ ITaskMonitor monitor) {
+ mInstalled.add(archive);
+ return true;
+ }
+ };
+ }
+
+ //------------
+
+ private class MockTaskFactory implements ITaskFactory {
+ public void start(String title, ITask task) {
+ new MockTask(task);
+ }
+ }
+
+ //------------
+
+ private static class MockTask implements ITaskMonitor {
+ public MockTask(ITask task) {
+ task.run(this);
+ }
+
+ public ITaskMonitor createSubMonitor(int tickCount) {
+ return this;
+ }
+
+ public boolean displayPrompt(String title, String message) {
+ return false;
+ }
+
+ public int getProgress() {
+ return 0;
+ }
+
+ public void incProgress(int delta) {
+ // ignore
+ }
+
+ public boolean isCancelRequested() {
+ return false;
+ }
+
+ public void setDescription(String format, Object... args) {
+ // ignore
+ }
+
+ public void setProgressMax(int max) {
+ // ignore
+ }
+
+ public void log(String format, Object... args) {
+ // ignore
+ }
+
+ public void logError(String format, Object... args) {
+ // ignore
+ }
+
+ public void logVerbose(String format, Object... args) {
+ // ignore
+ }
+ }
+
+ //------------
+
+ private static class NullImageFactory extends ImageFactory {
+ public NullImageFactory() {
+ // pass
+ super(null /*display*/);
+ }
+
+ @Override
+ public Image getImageByName(String imageName) {
+ return null;
+ }
+
+ @Override
+ public Image getImageForObject(Object object) {
+ return null;
+ }
+
+ @Override
+ public void dispose() {
+ // pass
+ }
+
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesDiffLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesDiffLogicTest.java
new file mode 100755
index 0000000..3be5d4f
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesDiffLogicTest.java
@@ -0,0 +1,789 @@
+/*
+ * Copyright (C) 2011 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.repository;
+
+import com.android.sdklib.internal.repository.MockAddonPackage;
+import com.android.sdklib.internal.repository.MockExtraPackage;
+import com.android.sdklib.internal.repository.MockPlatformPackage;
+import com.android.sdklib.internal.repository.MockPlatformToolPackage;
+import com.android.sdklib.internal.repository.MockToolPackage;
+import com.android.sdklib.internal.repository.Package;
+import com.android.sdklib.internal.repository.SdkRepoSource;
+import com.android.sdklib.internal.repository.SdkSource;
+import com.android.sdkuilib.internal.repository.PackageLoader.PkgItem;
+import com.android.sdkuilib.internal.repository.PackagesPage.PackagesDiffLogic;
+import com.android.sdkuilib.internal.repository.PackagesPage.PkgCategory;
+import com.android.sdkuilib.internal.repository.PackagesPage.PackagesDiffLogic.UpdateOp;
+
+import junit.framework.TestCase;
+
+public class PackagesDiffLogicTest extends TestCase {
+
+ private PackagesDiffLogic m;
+ private MockUpdaterData u;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ u = new MockUpdaterData();
+ m = new PackagesDiffLogic(u);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ // ----
+ //
+ // Test Details Note: the way load is implemented in PackageLoader, the
+ // loader processes each source and then for each source the packages are added
+ // to a list and the sorting algorithm is called with that list. Thus for
+ // one load, many calls to the sortByX/Y happen, with the list progressively
+ // being populated.
+ // However when the user switches sorting algorithm, the package list is not
+ // reloaded and is processed at once.
+
+ public void testSortByApi_Empty() {
+ UpdateOp op = m.updateStart(true /*sortByApi*/);
+ assertFalse(m.updateSourcePackages(op, null /*locals*/, new Package[0]));
+ assertFalse(m.updateEnd(op));
+
+ assertSame(m.mCurrentCategories, m.mApiCategories);
+
+ // We also keep these 2 categories even if they contain nothing
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=0>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_AddSamePackage() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+
+ UpdateOp op = m.updateStart(true /*sortByApi*/);
+ // First insert local packages
+ assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "some pkg", 1)
+ }));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'some pkg' rev=1>\n",
+ getTree(m));
+
+ // Insert the next source
+ // Same package as the one installed, so we don't display it
+ assertFalse(m.updateSourcePackages(op, src1, new Package[] {
+ new MockEmptyPackage(src1, "some pkg", 1)
+ }));
+
+ assertFalse(m.updateEnd(op));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'some pkg' rev=1>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_AddOtherPackage() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+
+ UpdateOp op = m.updateStart(true /*sortByApi*/);
+ // First insert local packages
+ assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "some pkg", 1)
+ }));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'some pkg' rev=1>\n",
+ getTree(m));
+
+ // Insert the next source
+ // Not the same package as the one installed, so we'll display it
+ assertTrue(m.updateSourcePackages(op, src1, new Package[] {
+ new MockEmptyPackage(src1, "other pkg", 1)
+ }));
+
+ assertFalse(m.updateEnd(op));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'some pkg' rev=1>\n" +
+ "-- <NEW, pkg:MockEmptyPackage 'other pkg' rev=1>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_Update1() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+
+ // Typical case: user has a locally installed package in revision 1
+ // The display list after sort should show that installed package.
+ UpdateOp op = m.updateStart(true /*sortByApi*/);
+ // First insert local packages
+ assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+
+ assertTrue(m.updateSourcePackages(op, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 4),
+ new MockEmptyPackage(src1, "type1", 2)
+ }));
+
+ assertFalse(m.updateEnd(op));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=4>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_Reload() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+
+ // First load reveals a package local package and its update
+ UpdateOp op1 = m.updateStart(true /*sortByApi*/);
+ // First insert local packages
+ assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+ assertTrue(m.updateSourcePackages(op1, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 2)
+ }));
+
+ assertFalse(m.updateEnd(op1));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=2>\n",
+ getTree(m));
+
+ // Now simulate a reload that clears the package list and create similar
+ // objects but not the same references. The only difference is that updateXyz
+ // returns false since they don't change anything.
+
+ UpdateOp op2 = m.updateStart(true /*sortByApi*/);
+ // First insert local packages
+ assertFalse(m.updateSourcePackages(op2, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+ assertFalse(m.updateSourcePackages(op2, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 2)
+ }));
+
+ assertFalse(m.updateEnd(op2));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=2>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_InstallPackage() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+
+ // First load reveals a new package
+ UpdateOp op1 = m.updateStart(true /*sortByApi*/);
+ // No local packages at first
+ assertFalse(m.updateSourcePackages(op1, null /*locals*/, new Package[0]));
+ assertTrue(m.updateSourcePackages(op1, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+
+ assertFalse(m.updateEnd(op1));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <NEW, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+
+ // Install it.
+ UpdateOp op2 = m.updateStart(true /*sortByApi*/);
+ // local packages
+ assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+ assertFalse(m.updateSourcePackages(op2, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+
+ assertFalse(m.updateEnd(op2));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+
+ // Load reveals an update
+ UpdateOp op3 = m.updateStart(true /*sortByApi*/);
+ // local packages
+ assertFalse(m.updateSourcePackages(op3, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+ assertTrue(m.updateSourcePackages(op3, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 2)
+ }));
+
+ assertFalse(m.updateEnd(op3));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=2>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_DeletePackage() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+
+ // We have an installed package
+ UpdateOp op2 = m.updateStart(true /*sortByApi*/);
+ // local packages
+ assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+ assertTrue(m.updateSourcePackages(op2, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 2)
+ }));
+
+ assertFalse(m.updateEnd(op2));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=2>\n",
+ getTree(m));
+
+ // User now deletes the installed package.
+ UpdateOp op1 = m.updateStart(true /*sortByApi*/);
+ // No local packages
+ assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[0]));
+ assertTrue(m.updateSourcePackages(op1, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1)
+ }));
+
+ assertFalse(m.updateEnd(op1));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=0>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <NEW, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_CompleteUpdate() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url1", "repo1");
+ SdkSource src2 = new SdkRepoSource("http://repo.com/url2", "repo2");
+
+ // Resulting categories are sorted by Tools, descending platform API and finally Extras.
+ // Addons are sorted by name within their API.
+ // Extras are sorted by vendor name.
+ // The order packages are added to the mAllPkgItems list is purposedly different from
+ // the final order we get.
+
+ // First update has the typical tools and a couple extras
+ UpdateOp op1 = m.updateStart(true /*sortByApi*/);
+
+ assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "android", "usb_driver", 4, 3),
+ }));
+ assertTrue(m.updateSourcePackages(op1, src1, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0),
+ new MockExtraPackage(src1, "android", "usb_driver", 5, 3),
+ }));
+ assertFalse(m.updateEnd(op1));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+
+ // Next update adds platforms and addon, sorted in a category based on their API level
+ UpdateOp op2 = m.updateStart(true /*sortByApi*/);
+ MockPlatformPackage p1;
+ MockPlatformPackage p2;
+
+ assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "android", "usb_driver", 4, 3),
+ // second update
+ p1 = new MockPlatformPackage(src1, 1, 2, 3), // API 1
+ new MockPlatformPackage(src1, 3, 6, 3),
+ new MockAddonPackage(src2, "addon A", p1, 5),
+ }));
+ assertTrue(m.updateSourcePackages(op2, src1, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0),
+ new MockExtraPackage(src1, "android", "usb_driver", 5, 3),
+ // second update
+ p2 = new MockPlatformPackage(src1, 2, 4, 3), // API 2
+ }));
+ assertTrue(m.updateSourcePackages(op2, src2, new Package[] {
+ new MockAddonPackage(src2, "addon C", p2, 9),
+ new MockAddonPackage(src2, "addon A", p1, 6),
+ new MockAddonPackage(src2, "addon B", p2, 7),
+ // the rev 8 update will be ignored since there's a rev 9 coming after
+ new MockAddonPackage(src2, "addon B", p2, 8),
+ new MockAddonPackage(src2, "addon B", p2, 9),
+ }));
+ assertFalse(m.updateEnd(op2));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgApiCategory <API=API 3, label=Android android-3 (API 3), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-3, API 3, revision 6>\n" +
+ "PkgApiCategory <API=API 2, label=Android android-2 (API 2), #items=3>\n" +
+ "-- <NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
+ "-- <NEW, pkg:addon B by vendor 2, Android API 2, revision 7, updated by:addon B by vendor 2, Android API 2, revision 9>\n" +
+ "-- <NEW, pkg:addon C by vendor 2, Android API 2, revision 9>\n" +
+ "PkgApiCategory <API=API 1, label=Android android-1 (API 1), #items=2>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "-- <INSTALLED, pkg:addon A by vendor 1, Android API 1, revision 5, updated by:addon A by vendor 1, Android API 1, revision 6>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+
+ // Reloading the same thing should have no impact except for the update methods
+ // returning false when they don't change the current list.
+ UpdateOp op3 = m.updateStart(true /*sortByApi*/);
+
+ assertFalse(m.updateSourcePackages(op3, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "android", "usb_driver", 4, 3),
+ // second update
+ p1 = new MockPlatformPackage(src1, 1, 2, 3),
+ new MockPlatformPackage(src1, 3, 6, 3),
+ new MockAddonPackage(src2, "addon A", p1, 5),
+ }));
+ assertFalse(m.updateSourcePackages(op3, src1, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0),
+ new MockExtraPackage(src1, "android", "usb_driver", 5, 3),
+ // second update
+ p2 = new MockPlatformPackage(src1, 2, 4, 3),
+ }));
+ assertTrue(m.updateSourcePackages(op3, src2, new Package[] {
+ new MockAddonPackage(src2, "addon C", p2, 9),
+ new MockAddonPackage(src2, "addon A", p1, 6),
+ new MockAddonPackage(src2, "addon B", p2, 7),
+ // the rev 8 update will be ignored since there's a rev 9 coming after
+ // however as a side effect it makes the update method return true as it
+ // incorporated the update.
+ new MockAddonPackage(src2, "addon B", p2, 8),
+ new MockAddonPackage(src2, "addon B", p2, 9),
+ }));
+ assertFalse(m.updateEnd(op3));
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgApiCategory <API=API 3, label=Android android-3 (API 3), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-3, API 3, revision 6>\n" +
+ "PkgApiCategory <API=API 2, label=Android android-2 (API 2), #items=3>\n" +
+ "-- <NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
+ "-- <NEW, pkg:addon B by vendor 2, Android API 2, revision 7, updated by:addon B by vendor 2, Android API 2, revision 9>\n" +
+ "-- <NEW, pkg:addon C by vendor 2, Android API 2, revision 9>\n" +
+ "PkgApiCategory <API=API 1, label=Android android-1 (API 1), #items=2>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "-- <INSTALLED, pkg:addon A by vendor 1, Android API 1, revision 5, updated by:addon A by vendor 1, Android API 1, revision 6>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+ }
+
+ // ----
+
+ public void testSortBySource_Empty() {
+ UpdateOp op = m.updateStart(false /*sortByApi*/);
+ assertFalse(m.updateSourcePackages(op, null /*locals*/, new Package[0]));
+ // UpdateEnd returns true since it removed the synthetic "unknown source" category
+ assertTrue(m.updateEnd(op));
+
+ assertSame(m.mCurrentCategories, m.mSourceCategories);
+ assertTrue(m.mApiCategories.isEmpty());
+
+ assertEquals(
+ "",
+ getTree(m));
+ }
+
+ public void testSortBySource_AddPackages() {
+ // Since we're sorting by source, items are grouped under their source
+ // even if installed. The 'local' source is only for installed items for
+ // which we don't know the source.
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+
+ UpdateOp op = m.updateStart(false /*sortByApi*/);
+ assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "known source", 2),
+ new MockEmptyPackage(null, "unknown source", 3),
+ }));
+
+ assertEquals(
+ "PkgSourceCategory <source=Local Packages (no.source), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'unknown source' rev=3>\n" +
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'known source' rev=2>\n",
+ getTree(m));
+
+ assertTrue(m.updateSourcePackages(op, src1, new Package[] {
+ new MockEmptyPackage(src1, "new", 1),
+ }));
+
+ assertFalse(m.updateEnd(op));
+
+ assertEquals(
+ "PkgSourceCategory <source=Local Packages (no.source), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'unknown source' rev=3>\n" +
+ "PkgSourceCategory <source=repo1 (repo.com), #items=2>\n" +
+ "-- <NEW, pkg:MockEmptyPackage 'new' rev=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'known source' rev=2>\n",
+ getTree(m));
+ }
+
+ public void testSortBySource_Update1() {
+
+ // Typical case: user has a locally installed package in revision 1
+ // The display list after sort should show that instaled package.
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+ UpdateOp op = m.updateStart(false /*sortByApi*/);
+ assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+
+ assertEquals(
+ "PkgSourceCategory <source=Local Packages (no.source), #items=0>\n" +
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+
+ // Edge case: the source reveals an update in revision 2. It is ignored since
+ // we already have a package in rev 4.
+
+ assertTrue(m.updateSourcePackages(op, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 4),
+ new MockEmptyPackage(src1, "type1", 2),
+ }));
+
+ assertTrue(m.updateEnd(op));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=4>\n",
+ getTree(m));
+ }
+
+ public void testSortBySource_Reload() {
+
+ // First load reveals a package local package and its update
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+ UpdateOp op1 = m.updateStart(false /*sortByApi*/);
+ assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertTrue(m.updateSourcePackages(op1, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 2),
+ }));
+ assertTrue(m.updateEnd(op1));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=2>\n",
+ getTree(m));
+
+ // Now simulate a reload that clears the package list and creates similar
+ // objects but not the same references. Update methods return false since
+ // they don't change anything.
+ UpdateOp op2 = m.updateStart(false /*sortByApi*/);
+ assertFalse(m.updateSourcePackages(op2, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertFalse(m.updateSourcePackages(op2, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 2),
+ }));
+ assertTrue(m.updateEnd(op2));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=2>\n",
+ getTree(m));
+ }
+
+ public void testSortBySource_InstallPackage() {
+
+ // First load reveals a new package
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+ UpdateOp op1 = m.updateStart(false /*sortByApi*/);
+ // no local package
+ assertFalse(m.updateSourcePackages(op1, null /*locals*/, new Package[0]));
+ assertTrue(m.updateSourcePackages(op1, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertTrue(m.updateEnd(op1));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <NEW, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+
+
+ // Install it. The display only shows the installed one, 'hiding' the remote package
+ UpdateOp op2 = m.updateStart(false /*sortByApi*/);
+ assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertFalse(m.updateSourcePackages(op2, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertTrue(m.updateEnd(op2));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+
+ // Now we have an update
+ UpdateOp op3 = m.updateStart(false /*sortByApi*/);
+ assertFalse(m.updateSourcePackages(op3, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertTrue(m.updateSourcePackages(op3, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 2),
+ }));
+ assertTrue(m.updateEnd(op3));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1, updated by:MockEmptyPackage 'type1' rev=2>\n",
+ getTree(m));
+ }
+
+ public void testSortBySource_DeletePackage() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1");
+
+ // Start with an installed package and its matching remote package
+ UpdateOp op2 = m.updateStart(false /*sortByApi*/);
+ assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertFalse(m.updateSourcePackages(op2, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertTrue(m.updateEnd(op2));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+
+ // User now deletes the installed package.
+ UpdateOp op1 = m.updateStart(false /*sortByApi*/);
+ // no local package
+ assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[0]));
+ assertFalse(m.updateSourcePackages(op1, src1, new Package[] {
+ new MockEmptyPackage(src1, "type1", 1),
+ }));
+ assertTrue(m.updateEnd(op1));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=1>\n" +
+ "-- <NEW, pkg:MockEmptyPackage 'type1' rev=1>\n",
+ getTree(m));
+ }
+
+ public void testSortBySource_CompleteUpdate() {
+ SdkSource src1 = new SdkRepoSource("http://repo.com/url1", "repo1");
+ SdkSource src2 = new SdkRepoSource("http://repo.com/url2", "repo2");
+
+ // First update has the typical tools and a couple extras
+ UpdateOp op1 = m.updateStart(false /*sortByApi*/);
+
+ assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "android", "usb_driver", 4, 3),
+ }));
+ assertTrue(m.updateSourcePackages(op1, src1, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0),
+ new MockExtraPackage(src1, "android", "usb_driver", 5, 3),
+ }));
+ assertTrue(m.updateEnd(op1));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=4>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+
+ // Next update adds platforms and addon, sorted in a category based on their API level
+ UpdateOp op2 = m.updateStart(false /*sortByApi*/);
+ MockPlatformPackage p1;
+ MockPlatformPackage p2;
+
+ assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "android", "usb_driver", 4, 3),
+ // second update
+ p1 = new MockPlatformPackage(src1, 1, 2, 3), // API 1
+ new MockPlatformPackage(src1, 3, 6, 3), // API 3
+ new MockAddonPackage(src2, "addon A", p1, 5),
+ }));
+ assertTrue(m.updateSourcePackages(op2, src1, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0),
+ new MockExtraPackage(src1, "android", "usb_driver", 5, 3),
+ // second update
+ p2 = new MockPlatformPackage(src1, 2, 4, 3), // API 2
+ }));
+ assertTrue(m.updateSourcePackages(op2, src2, new Package[] {
+ new MockAddonPackage(src2, "addon C", p2, 9),
+ new MockAddonPackage(src2, "addon A", p1, 6),
+ new MockAddonPackage(src2, "addon B", p2, 7),
+ // the rev 8 update will be ignored since there's a rev 9 coming after
+ new MockAddonPackage(src2, "addon B", p2, 8),
+ new MockAddonPackage(src2, "addon B", p2, 9),
+ }));
+ assertTrue(m.updateEnd(op2));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=7>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-3, API 3, revision 6>\n" +
+ "-- <NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n" +
+ "PkgSourceCategory <source=repo2 (repo.com), #items=3>\n" +
+ "-- <NEW, pkg:addon B by vendor 2, Android API 2, revision 7, updated by:addon B by vendor 2, Android API 2, revision 9>\n" +
+ "-- <NEW, pkg:addon C by vendor 2, Android API 2, revision 9>\n" +
+ "-- <INSTALLED, pkg:addon A by vendor 1, Android API 1, revision 5, updated by:addon A by vendor 1, Android API 1, revision 6>\n",
+ getTree(m));
+
+ // Reloading the same thing should have no impact except for the update methods
+ // returning false when they don't change the current list.
+ UpdateOp op3 = m.updateStart(false /*sortByApi*/);
+
+ assertFalse(m.updateSourcePackages(op3, null /*locals*/, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "android", "usb_driver", 4, 3),
+ // second update
+ p1 = new MockPlatformPackage(src1, 1, 2, 3),
+ new MockPlatformPackage(src1, 3, 6, 3),
+ new MockAddonPackage(src2, "addon A", p1, 5),
+ }));
+ assertFalse(m.updateSourcePackages(op3, src1, new Package[] {
+ new MockToolPackage(src1, 10, 3),
+ new MockPlatformToolPackage(src1, 3),
+ new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0),
+ new MockExtraPackage(src1, "android", "usb_driver", 5, 3),
+ // second update
+ p2 = new MockPlatformPackage(src1, 2, 4, 3),
+ }));
+ assertTrue(m.updateSourcePackages(op3, src2, new Package[] {
+ new MockAddonPackage(src2, "addon C", p2, 9),
+ new MockAddonPackage(src2, "addon A", p1, 6),
+ new MockAddonPackage(src2, "addon B", p2, 7),
+ // the rev 8 update will be ignored since there's a rev 9 coming after
+ // however as a side effect it makes the update method return true as it
+ // incorporated the update.
+ new MockAddonPackage(src2, "addon B", p2, 8),
+ new MockAddonPackage(src2, "addon B", p2, 9),
+ }));
+ assertTrue(m.updateEnd(op3));
+
+ assertEquals(
+ "PkgSourceCategory <source=repo1 (repo.com), #items=7>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-3, API 3, revision 6>\n" +
+ "-- <NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n" +
+ "PkgSourceCategory <source=repo2 (repo.com), #items=3>\n" +
+ "-- <NEW, pkg:addon B by vendor 2, Android API 2, revision 7, updated by:addon B by vendor 2, Android API 2, revision 9>\n" +
+ "-- <NEW, pkg:addon C by vendor 2, Android API 2, revision 9>\n" +
+ "-- <INSTALLED, pkg:addon A by vendor 1, Android API 1, revision 5, updated by:addon A by vendor 1, Android API 1, revision 6>\n",
+ getTree(m));
+ }
+
+ // ----
+
+ /**
+ * Simulates the display we would have in the Packages Tree.
+ * This always depends on mCurrentCategories like the tree does.
+ * The display format is something like:
+ * <pre>
+ * PkgCategory &lt;description&gt;
+ * -- &lt;PkgItem description&gt;
+ * </pre>
+ */
+ public String getTree(PackagesDiffLogic l) {
+ StringBuilder sb = new StringBuilder();
+
+ for (PkgCategory cat : l.mCurrentCategories) {
+ sb.append(cat.toString()).append('\n');
+ for (PkgItem item : cat.getItems()) {
+ sb.append("-- ").append(item.toString()).append('\n');
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java
index 9470f91..303c24f 100755
--- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java
@@ -16,23 +16,10 @@
package com.android.sdkuilib.internal.repository;
-import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.Archive;
-import com.android.sdklib.internal.repository.ArchiveInstaller;
-import com.android.sdklib.internal.repository.ITask;
-import com.android.sdklib.internal.repository.ITaskFactory;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.MockEmptySdkManager;
-import com.android.sdklib.internal.repository.Package;
-import com.android.sdklib.internal.repository.Archive.Arch;
-import com.android.sdklib.internal.repository.Archive.Os;
-import com.android.sdklib.mock.MockLog;
-import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
-import java.util.Properties;
import junit.framework.TestCase;
@@ -104,164 +91,4 @@ public class UpdaterDataTest extends TestCase {
assertEquals("[a1, a2]", Arrays.toString(m.getInstalled()));
}
- // ---
-
-
- /** A mock UpdaterData that simply records what would have been installed. */
- private static class MockUpdaterData extends UpdaterData {
-
- private final List<Archive> mInstalled = new ArrayList<Archive>();
-
- public MockUpdaterData() {
- super("/tmp/SDK", new MockLog());
-
- setTaskFactory(new MockTaskFactory());
- }
-
- /** Gives access to the internal {@link #installArchives(List)}. */
- public void _installArchives(List<ArchiveInfo> result) {
- installArchives(result);
- }
-
- public Archive[] getInstalled() {
- return mInstalled.toArray(new Archive[mInstalled.size()]);
- }
-
- @Override
- protected void initSdk() {
- setSdkManager(new MockEmptySdkManager("/tmp/SDK"));
- }
-
- @Override
- public void reloadSdk() {
- // bypass original implementation
- }
-
- /** Returns a mock installer that simply records what would have been installed. */
- @Override
- protected ArchiveInstaller createArchiveInstaler() {
- return new ArchiveInstaller() {
- @Override
- public boolean install(
- Archive archive,
- String osSdkRoot,
- boolean forceHttp,
- SdkManager sdkManager,
- ITaskMonitor monitor) {
- mInstalled.add(archive);
- return true;
- }
- };
- }
- }
-
- private static class MockTaskFactory implements ITaskFactory {
- public void start(String title, ITask task) {
- new MockTask(task);
- }
- }
-
- private static class MockTask implements ITaskMonitor {
- public MockTask(ITask task) {
- task.run(this);
- }
-
- public ITaskMonitor createSubMonitor(int tickCount) {
- return this;
- }
-
- public boolean displayPrompt(String title, String message) {
- return false;
- }
-
- public int getProgress() {
- return 0;
- }
-
- public void incProgress(int delta) {
- // ignore
- }
-
- public boolean isCancelRequested() {
- return false;
- }
-
- public void setDescription(String format, Object... args) {
- // ignore
- }
-
- public void setProgressMax(int max) {
- // ignore
- }
-
- public void log(String format, Object... args) {
- // ignore
- }
-
- public void logError(String format, Object... args) {
- // ignore
- }
-
- public void logVerbose(String format, Object... args) {
- // ignore
- }
- }
-
- private static class MockEmptyPackage extends Package {
- private final String mTestHandle;
-
- public MockEmptyPackage(String testHandle) {
- super(
- null /*source*/,
- null /*props*/,
- 0 /*revision*/,
- null /*license*/,
- null /*description*/,
- null /*descUrl*/,
- Os.ANY /*archiveOs*/,
- Arch.ANY /*archiveArch*/,
- null /*archiveOsPath*/
- );
- mTestHandle = testHandle;
- }
-
- @Override
- protected Archive createLocalArchive(
- Properties props,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- return new Archive(this, props, archiveOs, archiveArch, archiveOsPath) {
- @Override
- public String toString() {
- return mTestHandle;
- }
- };
- }
-
- public Archive getLocalArchive() {
- return getArchives()[0];
- }
-
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- return null;
- }
-
- @Override
- public String getListDescription() {
- return this.getClass().getSimpleName();
- }
-
- @Override
- public String getShortDescription() {
- return this.getClass().getSimpleName();
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- return false;
- }
-
- }
}