diff options
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 <nocompress> 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 <description> + * -- <PkgItem description> + * </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; - } - - } } |