private IProject guessProject(IStructuredSelection selection) {
    if (selection == null) {
      return null;
    }

    for (Object element : selection.toList()) {
      if (element instanceof IAdaptable) {
        IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class);
        IProject project = res != null ? res.getProject() : null;

        // Is this an Android project?
        try {
          if (project == null || !project.hasNature(AdtConstants.NATURE_DEFAULT)) {
            continue;
          }
        } catch (CoreException e) {
          // checking the nature failed, ignore this resource
          continue;
        }

        return project;
      } else if (element instanceof Pair<?, ?>) {
        // Pair of Project/String
        @SuppressWarnings("unchecked")
        Pair<IProject, String> pair = (Pair<IProject, String>) element;
        return pair.getFirst();
      }
    }

    // Try to figure out the project from the active editor
    IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
    if (window != null) {
      IWorkbenchPage page = window.getActivePage();
      if (page != null) {
        IEditorPart activeEditor = page.getActiveEditor();
        if (activeEditor instanceof AndroidXmlEditor) {
          Object input = ((AndroidXmlEditor) activeEditor).getEditorInput();
          if (input instanceof FileEditorInput) {
            FileEditorInput fileInput = (FileEditorInput) input;
            return fileInput.getFile().getProject();
          }
        }
      }
    }

    IJavaProject[] projects =
        BaseProjectHelper.getAndroidProjects(
            new IProjectFilter() {
              public boolean accept(IProject project) {
                return project.isAccessible();
              }
            });

    if (projects != null && projects.length == 1) {
      return projects[0].getProject();
    }

    return null;
  }
    /**
     * Displays a prompt message to the user and read two values, login/password.
     *
     * <p><i>Asks user for login/password information.</i>
     *
     * <p>This method shows a question in the standard output, asking for login and password.</br>
     * <b>Method Output:</b></br> Title</br> Message</br> Login: (Wait for user input)</br>
     * Password: (Wait for user input)</br>
     *
     * <p>
     *
     * @param title The title of the iteration.
     * @param message The message to be displayed.
     * @return A {@link Pair} holding the entered login and password. The <b>first element</b> is
     *     always the <b>Login</b>, and the <b>second element</b> is always the <b>Password</b>.
     *     This method will never return null, in case of error the pair will be filled with empty
     *     strings.
     * @see ITaskMonitor#displayLoginPasswordPrompt(String, String)
     */
    public Pair<String, String> displayLoginPasswordPrompt(String title, String message) {
      String login = ""; // $NON-NLS-1$
      String password = ""; // $NON-NLS-1$
      mSdkLog.printf("\n%1$s\n%2$s", title, message);
      byte[] readBuffer = new byte[2048];
      try {
        mSdkLog.printf("\nLogin: "******"\nPassword: "******""; // $NON-NLS-1$
        password = ""; // $NON-NLS-1$
        // Just print the error to console.
        mSdkLog.printf("\nError occurred during login/pass query: %s\n", e.getMessage());
      }

      return Pair.of(login, password);
    }
  /**
   * Returns a pair of (tag-balance,bracket-balance) for the range textStart to offset.
   *
   * @param doc the document
   * @param start the offset of the starting character (inclusive)
   * @param end the offset of the ending character (exclusive)
   * @return the balance of tags and brackets
   */
  private static Pair<Integer, Integer> getBalance(IStructuredDocument doc, int start, int end) {
    // Balance of open and closing tags
    // <foo></foo> has tagBalance = 0, <foo> has tagBalance = 1
    int tagBalance = 0;
    // Balance of open and closing brackets
    // <foo attr1="value1"> has bracketBalance = 1, <foo has bracketBalance = 1
    int bracketBalance = 0;
    IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(start);

    if (region != null) {
      boolean inOpenTag = true;
      while (region != null && region.getStartOffset() < end) {
        int regionStart = region.getStartOffset();
        ITextRegionList subRegions = region.getRegions();
        for (int i = 0, n = subRegions.size(); i < n; i++) {
          ITextRegion subRegion = subRegions.get(i);
          int subRegionStart = regionStart + subRegion.getStart();
          int subRegionEnd = regionStart + subRegion.getEnd();
          if (subRegionEnd < start || subRegionStart >= end) {
            continue;
          }
          String type = subRegion.getType();

          if (XML_TAG_OPEN.equals(type)) {
            bracketBalance++;
            inOpenTag = true;
          } else if (XML_TAG_CLOSE.equals(type)) {
            bracketBalance--;
            if (inOpenTag) {
              tagBalance++;
            } else {
              tagBalance--;
            }
          } else if (XML_END_TAG_OPEN.equals(type)) {
            bracketBalance++;
            inOpenTag = false;
          } else if (XML_EMPTY_TAG_CLOSE.equals(type)) {
            bracketBalance--;
          }
        }

        region = region.getNext();
      }
    }

    return Pair.of(tagBalance, bracketBalance);
  }
Exemple #4
0
  /**
   * Computes the minimum SDK and target SDK versions for the project
   *
   * @param project the project to look up the versions for
   * @return a pair of (minimum SDK, target SDK) versions, never null
   */
  @NonNull
  public static Pair<Integer, Integer> computeSdkVersions(IProject project) {
    int mMinSdkVersion = 1;
    int mTargetSdkVersion = 1;

    IAbstractFile manifestFile = AndroidManifest.getManifest(new IFolderWrapper(project));
    if (manifestFile != null) {
      try {
        Object value = AndroidManifest.getMinSdkVersion(manifestFile);
        mMinSdkVersion = 1; // Default case if missing
        if (value instanceof Integer) {
          mMinSdkVersion = ((Integer) value).intValue();
        } else if (value instanceof String) {
          // handle codename, only if we can resolve it.
          if (Sdk.getCurrent() != null) {
            IAndroidTarget target =
                Sdk.getCurrent().getTargetFromHashString("android-" + value); // $NON-NLS-1$
            if (target != null) {
              // codename future API level is current api + 1
              mMinSdkVersion = target.getVersion().getApiLevel() + 1;
            }
          }
        }

        Integer i = AndroidManifest.getTargetSdkVersion(manifestFile);
        if (i == null) {
          mTargetSdkVersion = mMinSdkVersion;
        } else {
          mTargetSdkVersion = i.intValue();
        }
      } catch (XPathExpressionException e) {
        // do nothing we'll use 1 below.
      } catch (StreamException e) {
        // do nothing we'll use 1 below.
      }
    }

    return Pair.of(mMinSdkVersion, mTargetSdkVersion);
  }
  private Pair<List<String>, List<String>> findViews(final boolean layoutsOnly) {
    final Set<String> customViews = new HashSet<String>();
    final Set<String> thirdPartyViews = new HashSet<String>();

    ProjectState state = Sdk.getProjectState(mProject);
    final List<IProject> libraries =
        state != null ? state.getFullLibraryProjects() : Collections.<IProject>emptyList();

    SearchRequestor requestor =
        new SearchRequestor() {
          @Override
          public void acceptSearchMatch(SearchMatch match) throws CoreException {
            // Ignore matches in comments
            if (match.isInsideDocComment()) {
              return;
            }

            Object element = match.getElement();
            if (element instanceof ResolvedBinaryType) {
              // Third party view
              ResolvedBinaryType type = (ResolvedBinaryType) element;
              IPackageFragment fragment = type.getPackageFragment();
              IPath path = fragment.getPath();
              String last = path.lastSegment();
              // Filter out android.jar stuff
              if (last.equals(FN_FRAMEWORK_LIBRARY)) {
                return;
              }
              if (!isValidView(type, layoutsOnly)) {
                return;
              }

              IProject matchProject = match.getResource().getProject();
              if (mProject == matchProject || libraries.contains(matchProject)) {
                String fqn = type.getFullyQualifiedName();
                thirdPartyViews.add(fqn);
              }
            } else if (element instanceof ResolvedSourceType) {
              // User custom view
              IProject matchProject = match.getResource().getProject();
              if (mProject == matchProject || libraries.contains(matchProject)) {
                ResolvedSourceType type = (ResolvedSourceType) element;
                if (!isValidView(type, layoutsOnly)) {
                  return;
                }
                String fqn = type.getFullyQualifiedName();
                fqn = fqn.replace('$', '.');
                customViews.add(fqn);
              }
            }
          }
        };
    try {
      IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
      if (javaProject != null) {
        String className = layoutsOnly ? CLASS_VIEWGROUP : CLASS_VIEW;
        IType viewType = javaProject.findType(className);
        if (viewType != null) {
          IJavaSearchScope scope = SearchEngine.createHierarchyScope(viewType);
          SearchParticipant[] participants =
              new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()};
          int matchRule = SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE;

          SearchPattern pattern =
              SearchPattern.createPattern(
                  "*", IJavaSearchConstants.CLASS, IJavaSearchConstants.IMPLEMENTORS, matchRule);
          SearchEngine engine = new SearchEngine();
          engine.search(pattern, participants, scope, requestor, new NullProgressMonitor());
        }
      }
    } catch (CoreException e) {
      AdtPlugin.log(e, null);
    }

    List<String> custom = new ArrayList<String>(customViews);
    List<String> thirdParty = new ArrayList<String>(thirdPartyViews);

    if (!layoutsOnly) {
      // Update our cached answers (unless we were filtered on only layouts)
      mCustomViews = custom;
      mThirdPartyViews = thirdParty;
    }

    return Pair.of(custom, thirdParty);
  }
  public void customizeDocumentCommand(IDocument document, DocumentCommand c) {
    if (!isSmartInsertMode()) {
      return;
    }

    if (!(document instanceof IStructuredDocument)) {
      // This shouldn't happen unless this strategy is used on an invalid document
      return;
    }
    IStructuredDocument doc = (IStructuredDocument) document;

    // Handle newlines/indentation
    if (c.length == 0
        && c.text != null
        && TextUtilities.endsWith(doc.getLegalLineDelimiters(), c.text) != -1) {

      IModelManager modelManager = StructuredModelManager.getModelManager();
      IStructuredModel model = modelManager.getModelForRead(doc);
      if (model != null) {
        try {
          final int offset = c.offset;
          int lineStart = findLineStart(doc, offset);
          int textStart = findTextStart(doc, lineStart, offset);

          IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(textStart);
          if (region != null && region.getType().equals(XML_TAG_NAME)) {
            Pair<Integer, Integer> balance = getBalance(doc, textStart, offset);
            int tagBalance = balance.getFirst();
            int bracketBalance = balance.getSecond();

            String lineIndent = ""; // $NON-NLS-1$
            if (textStart > lineStart) {
              lineIndent = doc.get(lineStart, textStart - lineStart);
            }

            // We only care if tag or bracket balance is greater than 0;
            // we never *dedent* on negative balances
            boolean addIndent = false;
            if (bracketBalance < 0) {
              // Handle
              //    <foo
              //        ></foo>^
              // and
              //    <foo
              //        />^
              ITextRegion left = getRegionAt(doc, offset, true /*biasLeft*/);
              if (left != null
                  && (left.getType().equals(XML_TAG_CLOSE)
                      || left.getType().equals(XML_EMPTY_TAG_CLOSE))) {

                // Find the corresponding open tag...
                // The org.eclipse.wst.xml.ui.gotoMatchingTag frequently
                // doesn't work, it just says "No matching brace found"
                // (or I would use that here).

                int targetBalance = 0;
                ITextRegion right = getRegionAt(doc, offset, false /*biasLeft*/);
                if (right != null && right.getType().equals(XML_END_TAG_OPEN)) {
                  targetBalance = -1;
                }
                int openTag =
                    AndroidXmlCharacterMatcher.findTagBackwards(doc, offset, targetBalance);
                if (openTag != -1) {
                  // Look up the indentation of the given line
                  lineIndent = AndroidXmlEditor.getIndentAtOffset(doc, openTag);
                }
              }
            } else if (tagBalance > 0 || bracketBalance > 0) {
              // Add indentation
              addIndent = true;
            }

            StringBuilder sb = new StringBuilder(c.text);
            sb.append(lineIndent);
            String oneIndentUnit = XmlFormatPreferences.create().getOneIndentUnit();
            if (addIndent) {
              sb.append(oneIndentUnit);
            }

            // Handle
            //     <foo>^</foo>
            // turning into
            //     <foo>
            //         ^
            //     </foo>
            ITextRegion left = getRegionAt(doc, offset, true /*biasLeft*/);
            ITextRegion right = getRegionAt(doc, offset, false /*biasLeft*/);
            if (left != null
                && right != null
                && left.getType().equals(XML_TAG_CLOSE)
                && right.getType().equals(XML_END_TAG_OPEN)) {
              // Move end tag
              if (tagBalance > 0 && bracketBalance < 0) {
                sb.append(oneIndentUnit);
              }
              c.caretOffset = offset + sb.length();
              c.shiftsCaret = false;
              sb.append(TextUtilities.getDefaultLineDelimiter(doc));
              sb.append(lineIndent);
            }
            c.text = sb.toString();
          } else if (region != null && region.getType().equals(XML_CONTENT)) {
            // Indenting in text content. If you're in the middle of editing
            // text, just copy the current line indentation.
            // However, if you're editing in leading whitespace (e.g. you press
            // newline on a blank line following say an element) then figure
            // out the indentation as if the newline had been pressed at the
            // end of the element, and insert that amount of indentation.
            // In this case we need to also make sure to subtract any existing
            // whitespace on the current line such that if we have
            //
            // <foo>
            // ^   <bar/>
            // </foo>
            //
            // you end up with
            //
            // <foo>
            //
            //    ^<bar/>
            // </foo>
            //
            String text = region.getText();
            int regionStart = region.getStartOffset();
            int delta = offset - regionStart;
            boolean inWhitespacePrefix = true;
            for (int i = 0, n = Math.min(delta, text.length()); i < n; i++) {
              char ch = text.charAt(i);
              if (!Character.isWhitespace(ch)) {
                inWhitespacePrefix = false;
                break;
              }
            }
            if (inWhitespacePrefix) {
              IStructuredDocumentRegion previous = region.getPrevious();
              if (previous != null && previous.getType() == XML_TAG_NAME) {
                ITextRegionList subRegions = previous.getRegions();
                ITextRegion last = subRegions.get(subRegions.size() - 1);
                if (last.getType() == XML_TAG_CLOSE || last.getType() == XML_EMPTY_TAG_CLOSE) {
                  int begin =
                      AndroidXmlCharacterMatcher.findTagBackwards(
                          doc, previous.getStartOffset() + last.getStart(), 0);
                  int prevLineStart = findLineStart(doc, begin);
                  int prevTextStart = findTextStart(doc, prevLineStart, begin);

                  String lineIndent = ""; // $NON-NLS-1$
                  if (prevTextStart > prevLineStart) {
                    lineIndent = doc.get(prevLineStart, prevTextStart - prevLineStart);
                  }
                  StringBuilder sb = new StringBuilder(c.text);
                  sb.append(lineIndent);
                  String oneIndentUnit = XmlFormatPreferences.create().getOneIndentUnit();

                  // See if there is whitespace on the insert line that
                  // we should also remove
                  for (int i = delta, n = text.length(); i < n; i++) {
                    char ch = text.charAt(i);
                    if (ch == ' ') {
                      c.length++;
                    } else {
                      break;
                    }
                  }
                  boolean onClosingTagLine = false;
                  if (text.indexOf('\n', delta) == -1) {
                    IStructuredDocumentRegion next = region.getNext();
                    if (next != null && next.getType() == XML_TAG_NAME) {
                      String nextType = next.getRegions().get(0).getType();
                      if (nextType == XML_END_TAG_OPEN) {
                        onClosingTagLine = true;
                      }
                    }
                  }

                  boolean addIndent = (last.getType() == XML_TAG_CLOSE) && !onClosingTagLine;
                  if (addIndent) {
                    sb.append(oneIndentUnit);
                  }
                  c.text = sb.toString();

                  return;
                }
              }
            }
            copyPreviousLineIndentation(doc, c);
          } else {
            copyPreviousLineIndentation(doc, c);
          }
        } catch (BadLocationException e) {
          AdtPlugin.log(e, null);
        } finally {
          model.releaseFromRead();
        }
      }
    }
  }
  Map<String, Map<String, BufferedImage>> generateImages(boolean previewOnly) {
    // Map of ids to images: Preserve insertion order (the densities)
    Map<String, Map<String, BufferedImage>> categoryMap =
        new LinkedHashMap<String, Map<String, BufferedImage>>();

    CreateAssetSetWizard wizard = (CreateAssetSetWizard) getWizard();
    AssetType type = wizard.getAssetType();
    boolean crop = mTrimCheckBox.getSelection();

    BufferedImage sourceImage = null;
    if (mImageRadio.getSelection()) {
      // Load the image
      // TODO: Only do this when the source image type is image
      String path = mImagePathText.getText().trim();
      if (path.length() == 0) {
        setErrorMessage("Enter a filename");
        return Collections.emptyMap();
      }
      File file = new File(path);
      if (!file.exists()) {
        setErrorMessage(String.format("%1$s does not exist", file.getPath()));
        return Collections.emptyMap();
      }

      setErrorMessage(null);
      sourceImage = getImage(path, false);
      if (sourceImage != null) {
        if (crop) {
          sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
        }
        int padding = getPadding();
        if (padding != 0) {
          sourceImage = Util.paddedImage(sourceImage, padding);
        }
      }
    } else if (mTextRadio.getSelection()) {
      String text = mText.getText();
      TextRenderUtil.Options options = new TextRenderUtil.Options();
      options.font = getSelectedFont();
      int color;
      if (type.needsColors()) {
        color = 0xFF000000 | (mFgColor.red << 16) | (mFgColor.green << 8) | mFgColor.blue;
      } else {
        color = 0xFFFFFFFF;
      }
      options.foregroundColor = color;
      sourceImage = TextRenderUtil.renderTextImage(text, getPadding(), options);

      if (crop) {
        sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
      }

      int padding = getPadding();
      if (padding != 0) {
        sourceImage = Util.paddedImage(sourceImage, padding);
      }
    } else {
      assert mClipartRadio.getSelection();
      assert mSelectedClipart != null;
      try {
        sourceImage = GraphicGenerator.getClipartImage(mSelectedClipart);

        if (crop) {
          sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
        }

        if (type.needsColors()) {
          int color = 0xFF000000 | (mFgColor.red << 16) | (mFgColor.green << 8) | mFgColor.blue;
          Paint paint = new java.awt.Color(color);
          sourceImage = Util.filledImage(sourceImage, paint);
        }

        int padding = getPadding();
        if (padding != 0) {
          sourceImage = Util.paddedImage(sourceImage, padding);
        }
      } catch (IOException e) {
        AdtPlugin.log(e, null);
        return categoryMap;
      }
    }

    GraphicGenerator generator = null;
    GraphicGenerator.Options options = null;
    switch (type) {
      case LAUNCHER:
        {
          generator = new LauncherIconGenerator();
          LauncherIconGenerator.LauncherOptions launcherOptions =
              new LauncherIconGenerator.LauncherOptions();
          launcherOptions.shape =
              mCircleButton.getSelection()
                  ? GraphicGenerator.Shape.CIRCLE
                  : GraphicGenerator.Shape.SQUARE;
          launcherOptions.crop = mCropRadio.getSelection();

          if (SUPPORT_LAUNCHER_ICON_TYPES) {
            launcherOptions.style =
                mFancyRadio.getSelection()
                    ? GraphicGenerator.Style.FANCY
                    : mGlossyRadio.getSelection()
                        ? GraphicGenerator.Style.GLOSSY
                        : GraphicGenerator.Style.SIMPLE;
          } else {
            launcherOptions.style = GraphicGenerator.Style.SIMPLE;
          }

          int color = (mBgColor.red << 16) | (mBgColor.green << 8) | mBgColor.blue;
          launcherOptions.backgroundColor = color;
          // Flag which tells the generator iterator to include a web graphic
          launcherOptions.isWebGraphic = !previewOnly;
          options = launcherOptions;

          break;
        }
      case MENU:
        generator = new MenuIconGenerator();
        options = new GraphicGenerator.Options();
        break;
      case ACTIONBAR:
        {
          generator = new ActionBarIconGenerator();
          ActionBarIconGenerator.ActionBarOptions actionBarOptions =
              new ActionBarIconGenerator.ActionBarOptions();
          actionBarOptions.theme =
              mHoloDarkRadio.getSelection()
                  ? ActionBarIconGenerator.Theme.HOLO_DARK
                  : ActionBarIconGenerator.Theme.HOLO_LIGHT;

          options = actionBarOptions;
          break;
        }
      case NOTIFICATION:
        {
          generator = new NotificationIconGenerator();
          NotificationIconGenerator.NotificationOptions notificationOptions =
              new NotificationIconGenerator.NotificationOptions();
          notificationOptions.shape =
              mCircleButton.getSelection()
                  ? GraphicGenerator.Shape.CIRCLE
                  : GraphicGenerator.Shape.SQUARE;
          options = notificationOptions;
          break;
        }
      case TAB:
        generator = new TabIconGenerator();
        options = new TabIconGenerator.TabOptions();
        break;
      default:
        AdtPlugin.log(IStatus.ERROR, "Unsupported asset type: %1$s", type);
        return categoryMap;
    }

    options.sourceImage = sourceImage;

    IProject project = wizard.getProject();
    Pair<Integer, Integer> v = ManifestInfo.computeSdkVersions(project);
    options.minSdk = v.getFirst();

    String baseName = wizard.getBaseName();
    generator.generate(null, categoryMap, this, options, baseName);

    return categoryMap;
  }
  public void testDropZones() {
    List<Pair<Point, String[]>> zones = new ArrayList<Pair<Point, String[]>>();

    zones.add(
        Pair.of(
            new Point(51 + 10, 181 + 10),
            new String[] {"above=@+id/Centered", "toLeftOf=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(71 + 10, 181 + 10),
            new String[] {"above=@+id/Centered", "alignLeft=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(104 + 10, 181 + 10),
            new String[] {"above=@+id/Centered", "alignRight=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(137 + 10, 181 + 10),
            new String[] {"above=@+id/Centered", "alignRight=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(170 + 10, 181 + 10),
            new String[] {"above=@+id/Centered", "toRightOf=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(51 + 10, 279 + 10),
            new String[] {"below=@+id/Centered", "toLeftOf=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(71 + 10, 279 + 10),
            new String[] {"below=@+id/Centered", "alignLeft=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(104 + 10, 279 + 10),
            new String[] {"below=@+id/Centered", "alignLeft=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(137 + 10, 279 + 10),
            new String[] {"below=@+id/Centered", "alignRight=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(170 + 10, 279 + 10),
            new String[] {"below=@+id/Centered", "toRightOf=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(51 + 10, 201 + 10),
            new String[] {"toLeftOf=@+id/Centered", "alignTop=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(51 + 10, 227 + 10),
            new String[] {"toLeftOf=@+id/Centered", "alignTop=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(170 + 10, 201 + 10),
            new String[] {"toRightOf=@+id/Centered", "alignTop=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(51 + 10, 253 + 10),
            new String[] {"toLeftOf=@+id/Centered", "alignBottom=@+id/Centered"}));
    zones.add(
        Pair.of(
            new Point(170 + 10, 227 + 10),
            new String[] {
              "toRightOf=@+id/Centered", "alignTop=@+id/Centered", "alignBottom=@+id/Centered"
            }));
    zones.add(
        Pair.of(
            new Point(170 + 10, 253 + 10),
            new String[] {"toRightOf=@+id/Centered", "alignBottom=@+id/Centered"}));

    for (Pair<Point, String[]> zonePair : zones) {
      Point dropPoint = zonePair.getFirst();
      String[] attachments = zonePair.getSecond();
      // If we drag right into the button itself, not a valid drop position

      INode inserted =
          dragInto(
              new Rect(0, 0, 105, 80),
              new Point(120, 240),
              dropPoint,
              1,
              -1,
              attachments,

              // Bounds rectangle
              "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",

              // Drop zones
              "useStyle(DROP_ZONE), "
                  + "drawRect(Rect[51,181,20,20]), drawRect(Rect[71,181,33,20]), "
                  + "drawRect(Rect[104,181,33,20]), drawRect(Rect[137,181,33,20]), "
                  + "drawRect(Rect[170,181,20,20]), drawRect(Rect[51,279,20,20]), "
                  + "drawRect(Rect[71,279,33,20]), drawRect(Rect[104,279,33,20]), "
                  + "drawRect(Rect[137,279,33,20]), drawRect(Rect[170,279,20,20]), "
                  + "drawRect(Rect[51,201,20,26]), drawRect(Rect[51,227,20,26]), "
                  + "drawRect(Rect[51,253,20,26]), drawRect(Rect[170,201,20,26]), "
                  + "drawRect(Rect[170,227,20,26]), drawRect(Rect[170,253,20,26])");

      for (String attachment : attachments) {
        String[] elements = attachment.split("=");
        String name = "layout_" + elements[0];
        String value = elements[1];
        assertEquals(value, inserted.getStringAttr(ANDROID_URI, name));
      }
    }
  }