aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java94
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java578
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditorContributor.java100
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java957
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestSourceViewerConfig.java43
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java628
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ClassAttributeDescriptor.java106
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestElementDescriptor.java123
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestPkgAttrDescriptor.java56
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PackageAttributeDescriptor.java41
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PostActivityCreationAction.java89
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PostReceiverCreationAction.java89
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ThemeAttributeDescriptor.java57
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java736
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestElementNode.java132
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java331
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiPackageAttributeNode.java321
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationAttributesPart.java175
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationPage.java136
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java312
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/InstrumentationPage.java102
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java123
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewInfoPart.java87
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewLinksPart.java124
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewPage.java165
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/PermissionPage.java111
26 files changed, 5816 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java
new file mode 100644
index 000000000..1492adbb7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest;
+
+import static com.android.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.utils.Pair;
+
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Content Assist Processor for AndroidManifest.xml
+ */
+@VisibleForTesting
+public final class ManifestContentAssist extends AndroidContentAssist {
+
+ /**
+ * Constructor for ManifestContentAssist
+ */
+ public ManifestContentAssist() {
+ super(AndroidTargetData.DESCRIPTOR_MANIFEST);
+ }
+
+ @Override
+ protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
+ String parentTagName, String attributeName, Node node, String wordPrefix,
+ boolean skipEndTag, int replaceLength) {
+ if (attributeName.endsWith(ATTRIBUTE_MIN_SDK_VERSION)
+ || attributeName.endsWith(ATTRIBUTE_TARGET_SDK_VERSION)) {
+ // The user is completing the minSdkVersion attribute: it should be
+ // an integer for the API version, but we'll add full Android version
+ // names to make it more obvious what they're selecting
+
+ List<Pair<String, String>> choices = new ArrayList<Pair<String, String>>();
+ int max = AdtUtils.getHighestKnownApiLevel();
+ // Look for any more recent installed versions the user may have
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk == null) {
+ return false;
+ }
+ IAndroidTarget[] targets = sdk.getTargets();
+ for (IAndroidTarget target : targets) {
+ AndroidVersion version = target.getVersion();
+ int apiLevel = version.getApiLevel();
+ if (apiLevel > max) {
+ if (version.isPreview()) {
+ // Use codename, not API level, as version string for preview versions
+ choices.add(Pair.of(version.getCodename(), version.getCodename()));
+ } else {
+ choices.add(Pair.of(Integer.toString(apiLevel), target.getFullName()));
+ }
+ }
+ }
+ for (int api = max; api >= 1; api--) {
+ String name = AdtUtils.getAndroidName(api);
+ choices.add(Pair.of(Integer.toString(api), name));
+ }
+ char needTag = 0;
+ addMatchingProposals(proposals, choices.toArray(), offset, node, wordPrefix,
+ needTag, true /* isAttribute */, false /* isNew */,
+ skipEndTag /* skipEndTag */, replaceLength);
+ return true;
+ } else {
+ return super.computeAttributeValues(proposals, offset, parentTagName, attributeName,
+ node, wordPrefix, skipEndTag, replaceLength);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java
new file mode 100644
index 000000000..55ebf5970
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors.USES_PERMISSION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.manifest.pages.ApplicationPage;
+import com.android.ide.eclipse.adt.internal.editors.manifest.pages.InstrumentationPage;
+import com.android.ide.eclipse.adt.internal.editors.manifest.pages.OverviewPage;
+import com.android.ide.eclipse.adt.internal.editors.manifest.pages.PermissionPage;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IMarkerDelta;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Multi-page form editor for AndroidManifest.xml.
+ */
+@SuppressWarnings("restriction")
+public final class ManifestEditor extends AndroidXmlEditor {
+
+ public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".manifest.ManifestEditor"; //$NON-NLS-1$
+
+ private final static String EMPTY = ""; //$NON-NLS-1$
+
+ /** Root node of the UI element hierarchy */
+ private UiElementNode mUiManifestNode;
+ /** The Application Page tab */
+ private ApplicationPage mAppPage;
+ /** The Overview Manifest Page tab */
+ private OverviewPage mOverviewPage;
+ /** The Permission Page tab */
+ private PermissionPage mPermissionPage;
+ /** The Instrumentation Page tab */
+ private InstrumentationPage mInstrumentationPage;
+
+ private IFileListener mMarkerMonitor;
+
+
+ /**
+ * Creates the form editor for AndroidManifest.xml.
+ */
+ public ManifestEditor() {
+ super();
+ addDefaultTargetListener();
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ GlobalProjectMonitor.getMonitor().removeFileListener(mMarkerMonitor);
+ }
+
+ @Override
+ public void activated() {
+ super.activated();
+ clearActionBindings(false);
+ }
+
+ @Override
+ public void deactivated() {
+ super.deactivated();
+ updateActionBindings();
+ }
+
+ @Override
+ protected void pageChange(int newPageIndex) {
+ super.pageChange(newPageIndex);
+ if (newPageIndex == mTextPageIndex) {
+ updateActionBindings();
+ } else {
+ clearActionBindings(false);
+ }
+ }
+
+ @Override
+ protected int getPersistenceCategory() {
+ return CATEGORY_MANIFEST;
+ }
+
+ /**
+ * Return the root node of the UI element hierarchy, which here
+ * is the "manifest" node.
+ */
+ @Override
+ public UiElementNode getUiRootNode() {
+ return mUiManifestNode;
+ }
+
+ /**
+ * Returns the Manifest descriptors for the file being edited.
+ */
+ public AndroidManifestDescriptors getManifestDescriptors() {
+ AndroidTargetData data = getTargetData();
+ if (data != null) {
+ return data.getManifestDescriptors();
+ }
+
+ return null;
+ }
+
+ // ---- Base Class Overrides ----
+
+ /**
+ * Returns whether the "save as" operation is supported by this editor.
+ * <p/>
+ * Save-As is a valid operation for the ManifestEditor since it acts on a
+ * single source file.
+ *
+ * @see IEditorPart
+ */
+ @Override
+ public boolean isSaveAsAllowed() {
+ return true;
+ }
+
+ @Override
+ public void doSave(IProgressMonitor monitor) {
+ // Look up the current (pre-save) values of minSdkVersion and targetSdkVersion
+ int prevMinSdkVersion = -1;
+ int prevTargetSdkVersion = -1;
+ IProject project = null;
+ ManifestInfo info = null;
+ try {
+ project = getProject();
+ if (project != null) {
+ info = ManifestInfo.get(project);
+ prevMinSdkVersion = info.getMinSdkVersion();
+ prevTargetSdkVersion = info.getTargetSdkVersion();
+ info.clear();
+ }
+ } catch (Throwable t) {
+ // We don't expect exceptions from the above calls, but we *really*
+ // need to make sure that nothing can prevent the save function from
+ // getting called!
+ AdtPlugin.log(t, null);
+ }
+
+ // Actually save
+ super.doSave(monitor);
+
+ // If the target/minSdkVersion has changed, clear all lint warnings (since many
+ // of them are tied to the min/target sdk levels), in order to avoid showing stale
+ // results
+ try {
+ if (info != null) {
+ int newMinSdkVersion = info.getMinSdkVersion();
+ int newTargetSdkVersion = info.getTargetSdkVersion();
+ if (newMinSdkVersion != prevMinSdkVersion
+ || newTargetSdkVersion != prevTargetSdkVersion) {
+ assert project != null;
+ EclipseLintClient.clearMarkers(project);
+ }
+ }
+ } catch (Throwable t) {
+ AdtPlugin.log(t, null);
+ }
+ }
+
+ /**
+ * Creates the various form pages.
+ */
+ @Override
+ protected void createFormPages() {
+ try {
+ addPage(mOverviewPage = new OverviewPage(this));
+ addPage(mAppPage = new ApplicationPage(this));
+ addPage(mPermissionPage = new PermissionPage(this));
+ addPage(mInstrumentationPage = new InstrumentationPage(this));
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$
+ }
+ }
+
+ /* (non-java doc)
+ * Change the tab/title name to include the project name.
+ */
+ @Override
+ protected void setInput(IEditorInput input) {
+ super.setInput(input);
+ IFile inputFile = getInputFile();
+ if (inputFile != null) {
+ startMonitoringMarkers();
+ setPartName(String.format("%1$s Manifest", inputFile.getProject().getName()));
+ }
+ }
+
+ /**
+ * Processes the new XML Model, which XML root node is given.
+ *
+ * @param xml_doc The XML document, if available, or null if none exists.
+ */
+ @Override
+ protected void xmlModelChanged(Document xml_doc) {
+ // create the ui root node on demand.
+ initUiRootNode(false /*force*/);
+
+ loadFromXml(xml_doc);
+ }
+
+ private void loadFromXml(Document xmlDoc) {
+ mUiManifestNode.setXmlDocument(xmlDoc);
+ Node node = getManifestXmlNode(xmlDoc);
+
+ if (node != null) {
+ // Refresh the manifest UI node and all its descendants
+ mUiManifestNode.loadFromXmlNode(node);
+ }
+ }
+
+ private Node getManifestXmlNode(Document xmlDoc) {
+ if (xmlDoc != null) {
+ ElementDescriptor manifestDesc = mUiManifestNode.getDescriptor();
+ String manifestXmlName = manifestDesc == null ? null : manifestDesc.getXmlName();
+ assert manifestXmlName != null;
+
+ if (manifestXmlName != null) {
+ Node node = xmlDoc.getDocumentElement();
+ if (node != null && manifestXmlName.equals(node.getNodeName())) {
+ return node;
+ }
+
+ for (node = xmlDoc.getFirstChild();
+ node != null;
+ node = node.getNextSibling()) {
+ if (node.getNodeType() == Node.ELEMENT_NODE &&
+ manifestXmlName.equals(node.getNodeName())) {
+ return node;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void onDescriptorsChanged() {
+ IStructuredModel model = getModelForRead();
+ if (model != null) {
+ try {
+ Node node = getManifestXmlNode(getXmlDocument(model));
+ mUiManifestNode.reloadFromXmlNode(node);
+ } finally {
+ model.releaseFromRead();
+ }
+ }
+
+ if (mOverviewPage != null) {
+ mOverviewPage.refreshUiApplicationNode();
+ }
+
+ if (mAppPage != null) {
+ mAppPage.refreshUiApplicationNode();
+ }
+
+ if (mPermissionPage != null) {
+ mPermissionPage.refreshUiNode();
+ }
+
+ if (mInstrumentationPage != null) {
+ mInstrumentationPage.refreshUiNode();
+ }
+ }
+
+ /**
+ * Reads and processes the current markers and adds a listener for marker changes.
+ */
+ private void startMonitoringMarkers() {
+ final IFile inputFile = getInputFile();
+ if (inputFile != null) {
+ updateFromExistingMarkers(inputFile);
+
+ mMarkerMonitor = new IFileListener() {
+ @Override
+ public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
+ if (isAndroidProject && file.equals(inputFile)) {
+ processMarkerChanges(markerDeltas);
+ }
+ }
+ };
+
+ GlobalProjectMonitor.getMonitor().addFileListener(
+ mMarkerMonitor, IResourceDelta.CHANGED);
+ }
+ }
+
+ /**
+ * Processes the markers of the specified {@link IFile} and updates the error status of
+ * {@link UiElementNode}s and {@link UiAttributeNode}s.
+ * @param inputFile the file being edited.
+ */
+ private void updateFromExistingMarkers(IFile inputFile) {
+ try {
+ // get the markers for the file
+ IMarker[] markers = inputFile.findMarkers(
+ AdtConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO);
+
+ AndroidManifestDescriptors desc = getManifestDescriptors();
+ if (desc != null) {
+ ElementDescriptor appElement = desc.getApplicationElement();
+
+ if (appElement != null && mUiManifestNode != null) {
+ UiElementNode appUiNode = mUiManifestNode.findUiChildNode(
+ appElement.getXmlName());
+ List<UiElementNode> children = appUiNode.getUiChildren();
+
+ for (IMarker marker : markers) {
+ processMarker(marker, children, IResourceDelta.ADDED);
+ }
+ }
+ }
+
+ } catch (CoreException e) {
+ // findMarkers can throw an exception, in which case, we'll do nothing.
+ }
+ }
+
+ /**
+ * Processes a {@link IMarker} change.
+ * @param markerDeltas the list of {@link IMarkerDelta}
+ */
+ private void processMarkerChanges(IMarkerDelta[] markerDeltas) {
+ AndroidManifestDescriptors descriptors = getManifestDescriptors();
+ if (descriptors != null && descriptors.getApplicationElement() != null) {
+ UiElementNode app_ui_node = mUiManifestNode.findUiChildNode(
+ descriptors.getApplicationElement().getXmlName());
+ List<UiElementNode> children = app_ui_node.getUiChildren();
+
+ for (IMarkerDelta markerDelta : markerDeltas) {
+ processMarker(markerDelta.getMarker(), children, markerDelta.getKind());
+ }
+ }
+ }
+
+ /**
+ * Processes a new/old/updated marker.
+ * @param marker The marker being added/removed/changed
+ * @param nodeList the list of activity/service/provider/receiver nodes.
+ * @param kind the change kind. Can be {@link IResourceDelta#ADDED},
+ * {@link IResourceDelta#REMOVED}, or {@link IResourceDelta#CHANGED}
+ */
+ private void processMarker(IMarker marker, List<UiElementNode> nodeList, int kind) {
+ // get the data from the marker
+ String nodeType = marker.getAttribute(AdtConstants.MARKER_ATTR_TYPE, EMPTY);
+ if (nodeType == EMPTY) {
+ return;
+ }
+
+ String className = marker.getAttribute(AdtConstants.MARKER_ATTR_CLASS, EMPTY);
+ if (className == EMPTY) {
+ return;
+ }
+
+ for (UiElementNode ui_node : nodeList) {
+ if (ui_node.getDescriptor().getXmlName().equals(nodeType)) {
+ for (UiAttributeNode attr : ui_node.getAllUiAttributes()) {
+ if (attr.getDescriptor().getXmlLocalName().equals(
+ AndroidManifestDescriptors.ANDROID_NAME_ATTR)) {
+ if (attr.getCurrentValue().equals(className)) {
+ if (kind == IResourceDelta.REMOVED) {
+ attr.setHasError(false);
+ } else {
+ attr.setHasError(true);
+ }
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates the initial UI Root Node, including the known mandatory elements.
+ * @param force if true, a new UiManifestNode is recreated even if it already exists.
+ */
+ @Override
+ protected void initUiRootNode(boolean force) {
+ // The manifest UI node is always created, even if there's no corresponding XML node.
+ if (mUiManifestNode != null && force == false) {
+ return;
+ }
+
+ AndroidManifestDescriptors manifestDescriptor = getManifestDescriptors();
+
+ if (manifestDescriptor != null) {
+ ElementDescriptor manifestElement = manifestDescriptor.getManifestElement();
+ mUiManifestNode = manifestElement.createUiNode();
+ mUiManifestNode.setEditor(this);
+
+ // Similarly, always create the /manifest/uses-sdk followed by /manifest/application
+ // (order of the elements now matters)
+ ElementDescriptor element = manifestDescriptor.getUsesSdkElement();
+ boolean present = false;
+ for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) {
+ if (ui_node.getDescriptor() == element) {
+ present = true;
+ break;
+ }
+ }
+ if (!present) {
+ mUiManifestNode.appendNewUiChild(element);
+ }
+
+ element = manifestDescriptor.getApplicationElement();
+ present = false;
+ for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) {
+ if (ui_node.getDescriptor() == element) {
+ present = true;
+ break;
+ }
+ }
+ if (!present) {
+ mUiManifestNode.appendNewUiChild(element);
+ }
+
+ onDescriptorsChanged();
+ } else {
+ // create a dummy descriptor/uinode until we have real descriptors
+ ElementDescriptor desc = new ElementDescriptor("manifest", //$NON-NLS-1$
+ "temporary descriptors due to missing decriptors", //$NON-NLS-1$
+ null /*tooltip*/, null /*sdk_url*/, null /*attributes*/,
+ null /*children*/, false /*mandatory*/);
+ mUiManifestNode = desc.createUiNode();
+ mUiManifestNode.setEditor(this);
+ }
+ }
+
+ /**
+ * Adds the given set of permissions into the manifest file in the suitable
+ * location
+ *
+ * @param permissions permission fqcn's to be added
+ * @param show if true, show one or more of the newly added permissions
+ */
+ public void addPermissions(@NonNull final List<String> permissions, final boolean show) {
+ wrapUndoEditXmlModel("Add permissions", new Runnable() {
+ @Override
+ public void run() {
+ // Ensure that the model is current:
+ initUiRootNode(true /*force*/);
+ UiElementNode root = getUiRootNode();
+
+ ElementDescriptor descriptor = getManifestDescriptors().getUsesPermissionElement();
+ boolean shown = false;
+ for (String permission : permissions) {
+ // Find the first permission which sorts alphabetically laster than
+ // this permission (or the last permission, if none are after in the alphabet)
+ // and insert it there
+ int lastPermissionIndex = -1;
+ int nextPermissionIndex = -1;
+ int index = 0;
+ for (UiElementNode sibling : root.getUiChildren()) {
+ Node node = sibling.getXmlNode();
+ if (node.getNodeName().equals(USES_PERMISSION)) {
+ lastPermissionIndex = index;
+ String name = ((Element) node).getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (permission.compareTo(name) < 0) {
+ nextPermissionIndex = index;
+ break;
+ }
+ } else if (node.getNodeName().equals("application")) { //$NON-NLS-1$
+ // permissions should come before the application element
+ nextPermissionIndex = index;
+ break;
+ }
+ index++;
+ }
+
+ if (nextPermissionIndex != -1) {
+ index = nextPermissionIndex;
+ } else if (lastPermissionIndex != -1) {
+ index = lastPermissionIndex + 1;
+ } else {
+ index = root.getUiChildren().size();
+ }
+ UiElementNode usesPermission = root.insertNewUiChild(index, descriptor);
+ usesPermission.setAttributeValue(ATTR_NAME, ANDROID_URI, permission,
+ true /*override*/);
+ Node node = usesPermission.createXmlNode();
+ if (show && !shown) {
+ shown = true;
+ if (node instanceof IndexedRegion && getInputFile() != null) {
+ IndexedRegion indexedRegion = (IndexedRegion) node;
+ IRegion region = new Region(indexedRegion.getStartOffset(),
+ indexedRegion.getEndOffset() - indexedRegion.getStartOffset());
+ try {
+ AdtPlugin.openFile(getInputFile(), region, true /*show*/);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, null);
+ }
+ } else {
+ show(node);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes the permissions from the manifest editor
+ *
+ * @param permissions the permission fqcn's to be removed
+ */
+ public void removePermissions(@NonNull final Collection<String> permissions) {
+ wrapUndoEditXmlModel("Remove permissions", new Runnable() {
+ @Override
+ public void run() {
+ // Ensure that the model is current:
+ initUiRootNode(true /*force*/);
+ UiElementNode root = getUiRootNode();
+
+ for (String permission : permissions) {
+ for (UiElementNode sibling : root.getUiChildren()) {
+ Node node = sibling.getXmlNode();
+ if (node.getNodeName().equals(USES_PERMISSION)) {
+ String name = ((Element) node).getAttributeNS(ANDROID_URI, ATTR_NAME);
+ if (name.equals(permission)) {
+ sibling.deleteXmlNode();
+ break;
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditorContributor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditorContributor.java
new file mode 100644
index 000000000..8beca30b8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditorContributor.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.actions.ActionFactory;
+import org.eclipse.ui.ide.IDEActionFactory;
+import org.eclipse.ui.part.MultiPageEditorActionBarContributor;
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.ui.texteditor.ITextEditorActionConstants;
+
+/**
+ * Manages the installation/deinstallation of global actions for multi-page
+ * editors. Responsible for the redirection of global actions to the active
+ * editor. Multi-page contributor replaces the contributors for the individual
+ * editors in the multi-page editor.
+ *
+ * TODO: Doesn't look like we need this. Remove it if not needed.
+ * @deprecated
+ */
+final class ManifestEditorContributor extends MultiPageEditorActionBarContributor {
+ private IEditorPart mActiveEditorPart;
+
+ /**
+ * Creates a multi-page contributor.
+ *
+ * Marked as Private so it can't be instanciated. This is a cheap way to make sure
+ * it's not being used. As noted in constructor, should be removed if not used.
+ * @deprecated
+ */
+ private ManifestEditorContributor() {
+ super();
+ }
+
+ /**
+ * Returns the action registed with the given text editor.
+ *
+ * @return IAction or null if editor is null.
+ */
+ protected IAction getAction(ITextEditor editor, String actionID) {
+ return (editor == null ? null : editor.getAction(actionID));
+ }
+
+ /*
+ * (non-JavaDoc) Method declared in
+ * AbstractMultiPageEditorActionBarContributor.
+ */
+
+ @Override
+ public void setActivePage(IEditorPart part) {
+ if (mActiveEditorPart == part)
+ return;
+
+ mActiveEditorPart = part;
+
+ IActionBars actionBars = getActionBars();
+ if (actionBars != null) {
+
+ ITextEditor editor =
+ (part instanceof ITextEditor) ? (ITextEditor)part : null;
+
+ actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(),
+ getAction(editor, ITextEditorActionConstants.DELETE));
+ actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(),
+ getAction(editor, ITextEditorActionConstants.UNDO));
+ actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(),
+ getAction(editor, ITextEditorActionConstants.REDO));
+ actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(),
+ getAction(editor, ITextEditorActionConstants.CUT));
+ actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(),
+ getAction(editor, ITextEditorActionConstants.COPY));
+ actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(),
+ getAction(editor, ITextEditorActionConstants.PASTE));
+ actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
+ getAction(editor, ITextEditorActionConstants.SELECT_ALL));
+ actionBars.setGlobalActionHandler(ActionFactory.FIND.getId(),
+ getAction(editor, ITextEditorActionConstants.FIND));
+ actionBars.setGlobalActionHandler(
+ IDEActionFactory.BOOKMARK.getId(), getAction(editor,
+ IDEActionFactory.BOOKMARK.getId()));
+ actionBars.updateActionBars();
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
new file mode 100644
index 000000000..6d2d1c1f2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
@@ -0,0 +1,957 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest;
+
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.CLASS_ACTIVITY;
+import static com.android.SdkConstants.NS_RESOURCES;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_ICON;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_LABEL;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_PACKAGE;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_PARENT_ACTIVITY_NAME;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_SUPPORTS_RTL;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_THEME;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_UI_OPTIONS;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_VALUE;
+import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
+import static com.android.xml.AndroidManifest.NODE_METADATA;
+import static com.android.xml.AndroidManifest.NODE_USES_SDK;
+import static com.android.xml.AndroidManifest.VALUE_PARENT_ACTIVITY;
+import static org.eclipse.jdt.core.search.IJavaSearchConstants.REFERENCES;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.io.IFolderWrapper;
+import com.android.io.IAbstractFile;
+import com.android.io.StreamException;
+import com.android.resources.ScreenSize;
+import com.android.sdklib.IAndroidTarget;
+import com.android.utils.Pair;
+import com.android.xml.AndroidManifest;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.core.search.SearchMatch;
+import org.eclipse.jdt.core.search.SearchParticipant;
+import org.eclipse.jdt.core.search.SearchPattern;
+import org.eclipse.jdt.core.search.SearchRequestor;
+import org.eclipse.jdt.internal.core.BinaryType;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.ui.editors.text.TextFileDocumentProvider;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPathExpressionException;
+
+/**
+ * Retrieves and caches manifest information such as the themes to be used for
+ * a given activity.
+ *
+ * @see AndroidManifest
+ */
+public class ManifestInfo {
+
+ public static class ActivityAttributes {
+ @Nullable
+ private final String mIcon;
+ @Nullable
+ private final String mLabel;
+ @NonNull
+ private final String mName;
+ @Nullable
+ private final String mParentActivity;
+ @Nullable
+ private final String mTheme;
+ @Nullable
+ private final String mUiOptions;
+
+ public ActivityAttributes(Element activity, String packageName) {
+
+ // Get activity name.
+ String name = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_NAME);
+ if (name == null || name.length() == 0) {
+ throw new RuntimeException("Activity name cannot be empty");
+ }
+ int index = name.indexOf('.');
+ if (index <= 0 && packageName != null && !packageName.isEmpty()) {
+ name = packageName + (index == -1 ? "." : "") + name;
+ }
+ mName = name;
+
+ // Get activity icon.
+ String value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON);
+ if (value != null && value.length() > 0) {
+ mIcon = value;
+ } else {
+ mIcon = null;
+ }
+
+ // Get activity label.
+ value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL);
+ if (value != null && value.length() > 0) {
+ mLabel = value;
+ } else {
+ mLabel = null;
+ }
+
+ // Get activity parent. Also search the meta-data for parent info.
+ value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_PARENT_ACTIVITY_NAME);
+ if (value == null || value.length() == 0) {
+ // TODO: Not sure if meta data can be used for API Level > 16
+ NodeList metaData = activity.getElementsByTagName(NODE_METADATA);
+ for (int j = 0, m = metaData.getLength(); j < m; j++) {
+ Element data = (Element) metaData.item(j);
+ String metadataName = data.getAttributeNS(NS_RESOURCES, ATTRIBUTE_NAME);
+ if (VALUE_PARENT_ACTIVITY.equals(metadataName)) {
+ value = data.getAttributeNS(NS_RESOURCES, ATTRIBUTE_VALUE);
+ if (value != null) {
+ index = value.indexOf('.');
+ if (index <= 0 && packageName != null && !packageName.isEmpty()) {
+ value = packageName + (index == -1 ? "." : "") + value;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (value != null && value.length() > 0) {
+ mParentActivity = value;
+ } else {
+ mParentActivity = null;
+ }
+
+ // Get activity theme.
+ value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
+ if (value != null && value.length() > 0) {
+ mTheme = value;
+ } else {
+ mTheme = null;
+ }
+
+ // Get UI options.
+ value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_UI_OPTIONS);
+ if (value != null && value.length() > 0) {
+ mUiOptions = value;
+ } else {
+ mUiOptions = null;
+ }
+ }
+
+ @Nullable
+ public String getIcon() {
+ return mIcon;
+ }
+
+ @Nullable
+ public String getLabel() {
+ return mLabel;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ @Nullable
+ public String getParentActivity() {
+ return mParentActivity;
+ }
+
+ @Nullable
+ public String getTheme() {
+ return mTheme;
+ }
+
+ @Nullable
+ public String getUiOptions() {
+ return mUiOptions;
+ }
+ }
+
+ /**
+ * The maximum number of milliseconds to search for an activity in the codebase when
+ * attempting to associate layouts with activities in
+ * {@link #guessActivity(IFile, String)}
+ */
+ private static final int SEARCH_TIMEOUT_MS = 3000;
+
+ private final IProject mProject;
+ private String mPackage;
+ private String mManifestTheme;
+ private Map<String, ActivityAttributes> mActivityAttributes;
+ private IAbstractFile mManifestFile;
+ private long mLastModified;
+ private long mLastChecked;
+ private String mMinSdkName;
+ private int mMinSdk;
+ private int mTargetSdk;
+ private String mApplicationIcon;
+ private String mApplicationLabel;
+ private boolean mApplicationSupportsRtl;
+
+ /**
+ * Qualified name for the per-project non-persistent property storing the
+ * {@link ManifestInfo} for this project
+ */
+ final static QualifiedName MANIFEST_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID,
+ "manifest"); //$NON-NLS-1$
+
+ /**
+ * Constructs an {@link ManifestInfo} for the given project. Don't use this method;
+ * use the {@link #get} factory method instead.
+ *
+ * @param project project to create an {@link ManifestInfo} for
+ */
+ private ManifestInfo(IProject project) {
+ mProject = project;
+ }
+
+ /**
+ * Clears the cached manifest information. The next get call on one of the
+ * properties will cause the information to be refreshed.
+ */
+ public void clear() {
+ mLastChecked = 0;
+ }
+
+ /**
+ * Returns the {@link ManifestInfo} for the given project
+ *
+ * @param project the project the finder is associated with
+ * @return a {@ManifestInfo} for the given project, never null
+ */
+ @NonNull
+ public static ManifestInfo get(IProject project) {
+ ManifestInfo finder = null;
+ try {
+ finder = (ManifestInfo) project.getSessionProperty(MANIFEST_FINDER);
+ } catch (CoreException e) {
+ // Not a problem; we will just create a new one
+ }
+
+ if (finder == null) {
+ finder = new ManifestInfo(project);
+ try {
+ project.setSessionProperty(MANIFEST_FINDER, finder);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, "Can't store ManifestInfo");
+ }
+ }
+
+ return finder;
+ }
+
+ /**
+ * Ensure that the package, theme and activity maps are initialized and up to date
+ * with respect to the manifest file
+ */
+ private void sync() {
+ // Since each of the accessors call sync(), allow a bunch of immediate
+ // accessors to all bypass the file stat() below
+ long now = System.currentTimeMillis();
+ if (now - mLastChecked < 50 && mManifestFile != null) {
+ return;
+ }
+ mLastChecked = now;
+
+ if (mManifestFile == null) {
+ IFolderWrapper projectFolder = new IFolderWrapper(mProject);
+ mManifestFile = AndroidManifest.getManifest(projectFolder);
+ if (mManifestFile == null) {
+ return;
+ }
+ }
+
+ // Check to see if our data is up to date
+ long fileModified = mManifestFile.getModificationStamp();
+ if (fileModified == mLastModified) {
+ // Already have up to date data
+ return;
+ }
+ mLastModified = fileModified;
+
+ mActivityAttributes = new HashMap<String, ActivityAttributes>();
+ mManifestTheme = null;
+ mTargetSdk = 1; // Default when not specified
+ mMinSdk = 1; // Default when not specified
+ mMinSdkName = "1"; // Default when not specified
+ mPackage = ""; //$NON-NLS-1$
+ mApplicationIcon = null;
+ mApplicationLabel = null;
+ mApplicationSupportsRtl = false;
+
+ Document document = null;
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ InputSource is = new InputSource(mManifestFile.getContents());
+
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ document = builder.parse(is);
+
+ Element root = document.getDocumentElement();
+ mPackage = root.getAttribute(ATTRIBUTE_PACKAGE);
+ NodeList activities = document.getElementsByTagName(NODE_ACTIVITY);
+ for (int i = 0, n = activities.getLength(); i < n; i++) {
+ Element activity = (Element) activities.item(i);
+ ActivityAttributes info = new ActivityAttributes(activity, mPackage);
+ mActivityAttributes.put(info.getName(), info);
+ }
+
+ NodeList applications = root.getElementsByTagName(AndroidManifest.NODE_APPLICATION);
+ if (applications.getLength() > 0) {
+ assert applications.getLength() == 1;
+ Element application = (Element) applications.item(0);
+ if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON)) {
+ mApplicationIcon = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON);
+ }
+ if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL)) {
+ mApplicationLabel = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL);
+ }
+ if (SdkConstants.VALUE_TRUE.equals(application.getAttributeNS(NS_RESOURCES,
+ ATTRIBUTE_SUPPORTS_RTL))) {
+ mApplicationSupportsRtl = true;
+ }
+
+ String defaultTheme = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
+ if (defaultTheme != null && !defaultTheme.isEmpty()) {
+ // From manifest theme documentation:
+ // "If that attribute is also not set, the default system theme is used."
+ mManifestTheme = defaultTheme;
+ }
+ }
+
+ // Look up target SDK
+ NodeList usesSdks = root.getElementsByTagName(NODE_USES_SDK);
+ if (usesSdks.getLength() > 0) {
+ Element usesSdk = (Element) usesSdks.item(0);
+ mMinSdk = getApiVersion(usesSdk, ATTRIBUTE_MIN_SDK_VERSION, 1);
+ mTargetSdk = getApiVersion(usesSdk, ATTRIBUTE_TARGET_SDK_VERSION, mMinSdk);
+ }
+
+ } catch (SAXException e) {
+ AdtPlugin.log(e, "Malformed manifest");
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Could not read Manifest data");
+ }
+ }
+
+ private int getApiVersion(Element usesSdk, String attribute, int defaultApiLevel) {
+ String valueString = null;
+ if (usesSdk.hasAttributeNS(NS_RESOURCES, attribute)) {
+ valueString = usesSdk.getAttributeNS(NS_RESOURCES, attribute);
+ if (attribute.equals(ATTRIBUTE_MIN_SDK_VERSION)) {
+ mMinSdkName = valueString;
+ }
+ }
+
+ if (valueString != null) {
+ int apiLevel = -1;
+ try {
+ apiLevel = Integer.valueOf(valueString);
+ } catch (NumberFormatException e) {
+ // Handle codename
+ if (Sdk.getCurrent() != null) {
+ IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
+ "android-" + valueString); //$NON-NLS-1$
+ if (target != null) {
+ // codename future API level is current api + 1
+ apiLevel = target.getVersion().getApiLevel() + 1;
+ }
+ }
+ }
+
+ return apiLevel;
+ }
+
+ return defaultApiLevel;
+ }
+
+ /**
+ * Returns the default package registered in the Android manifest
+ *
+ * @return the default package registered in the manifest
+ */
+ @NonNull
+ public String getPackage() {
+ sync();
+ return mPackage;
+ }
+
+ /**
+ * Returns a map from activity full class names to the corresponding {@link ActivityAttributes}.
+ *
+ * @return a map from activity fqcn to ActivityAttributes
+ */
+ @NonNull
+ public Map<String, ActivityAttributes> getActivityAttributesMap() {
+ sync();
+ return mActivityAttributes;
+ }
+
+ /**
+ * Returns the attributes of an activity given its full class name.
+ */
+ @Nullable
+ public ActivityAttributes getActivityAttributes(String activity) {
+ return getActivityAttributesMap().get(activity);
+ }
+
+ /**
+ * Returns the manifest theme registered on the application, if any
+ *
+ * @return a manifest theme, or null if none was registered
+ */
+ @Nullable
+ public String getManifestTheme() {
+ sync();
+ return mManifestTheme;
+ }
+
+ /**
+ * Returns the default theme for this project, by looking at the manifest default
+ * theme registration, target SDK, rendering target, etc.
+ *
+ * @param renderingTarget the rendering target use to render the theme, or null
+ * @param screenSize the screen size to obtain a default theme for, or null if unknown
+ * @return the theme to use for this project, never null
+ */
+ @NonNull
+ public String getDefaultTheme(IAndroidTarget renderingTarget, ScreenSize screenSize) {
+ sync();
+
+ if (mManifestTheme != null) {
+ return mManifestTheme;
+ }
+
+ int renderingTargetSdk = mTargetSdk;
+ if (renderingTarget != null) {
+ renderingTargetSdk = renderingTarget.getVersion().getApiLevel();
+ }
+
+ int apiLevel = Math.min(mTargetSdk, renderingTargetSdk);
+ // For now this theme works only on XLARGE screens. When it works for all sizes,
+ // add that new apiLevel to this check.
+ if (apiLevel >= 11 && screenSize == ScreenSize.XLARGE || apiLevel >= 14) {
+ return ANDROID_STYLE_RESOURCE_PREFIX + "Theme.Holo"; //$NON-NLS-1$
+ } else {
+ return ANDROID_STYLE_RESOURCE_PREFIX + "Theme"; //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Returns the application icon, or null
+ *
+ * @return the application icon, or null
+ */
+ @Nullable
+ public String getApplicationIcon() {
+ sync();
+ return mApplicationIcon;
+ }
+
+ /**
+ * Returns the application label, or null
+ *
+ * @return the application label, or null
+ */
+ @Nullable
+ public String getApplicationLabel() {
+ sync();
+ return mApplicationLabel;
+ }
+
+ /**
+ * Returns true if the application has RTL support.
+ *
+ * @return true if the application has RTL support.
+ */
+ public boolean isRtlSupported() {
+ sync();
+ return mApplicationSupportsRtl;
+ }
+
+ /**
+ * Returns the target SDK version
+ *
+ * @return the target SDK version
+ */
+ public int getTargetSdkVersion() {
+ sync();
+ return mTargetSdk;
+ }
+
+ /**
+ * Returns the minimum SDK version
+ *
+ * @return the minimum SDK version
+ */
+ public int getMinSdkVersion() {
+ sync();
+ return mMinSdk;
+ }
+
+ /**
+ * Returns the minimum SDK version name (which may not be a numeric string, e.g.
+ * it could be a codename). It will never be null or empty; if no min sdk version
+ * was specified in the manifest, the return value will be "1". Use
+ * {@link #getMinSdkCodeName()} instead if you want to look up whether there is a code name.
+ *
+ * @return the minimum SDK version
+ */
+ @NonNull
+ public String getMinSdkName() {
+ sync();
+ if (mMinSdkName == null || mMinSdkName.isEmpty()) {
+ mMinSdkName = "1"; //$NON-NLS-1$
+ }
+
+ return mMinSdkName;
+ }
+
+ /**
+ * Returns the code name used for the minimum SDK version, if any.
+ *
+ * @return the minSdkVersion codename or null
+ */
+ @Nullable
+ public String getMinSdkCodeName() {
+ String minSdkName = getMinSdkName();
+ if (!Character.isDigit(minSdkName.charAt(0))) {
+ return minSdkName;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link IPackageFragment} for the package registered in the manifest
+ *
+ * @return the {@link IPackageFragment} for the package registered in the manifest
+ */
+ @Nullable
+ public IPackageFragment getPackageFragment() {
+ sync();
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
+ if (javaProject != null) {
+ IPackageFragmentRoot root = ManifestInfo.getSourcePackageRoot(javaProject);
+ if (root != null) {
+ return root.getPackageFragment(mPackage);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the activity associated with the given layout file. Makes an educated guess
+ * by peeking at the usages of the R.layout.name field corresponding to the layout and
+ * if it finds a usage.
+ *
+ * @param project the project containing the layout
+ * @param layoutName the layout whose activity we want to look up
+ * @param pkg the package containing activities
+ * @return the activity name
+ */
+ @Nullable
+ public static String guessActivity(IProject project, String layoutName, String pkg) {
+ List<String> activities = guessActivities(project, layoutName, pkg);
+ if (activities.size() > 0) {
+ return activities.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the activities associated with the given layout file. Makes an educated guess
+ * by peeking at the usages of the R.layout.name field corresponding to the layout and
+ * if it finds a usage.
+ *
+ * @param project the project containing the layout
+ * @param layoutName the layout whose activity we want to look up
+ * @param pkg the package containing activities
+ * @return the activity name
+ */
+ @NonNull
+ public static List<String> guessActivities(IProject project, String layoutName, String pkg) {
+ final LinkedList<String> activities = new LinkedList<String>();
+ SearchRequestor requestor = new SearchRequestor() {
+ @Override
+ public void acceptSearchMatch(SearchMatch match) throws CoreException {
+ Object element = match.getElement();
+ if (element instanceof IMethod) {
+ IMethod method = (IMethod) element;
+ IType declaringType = method.getDeclaringType();
+ String fqcn = declaringType.getFullyQualifiedName();
+
+ if ((declaringType.getSuperclassName() != null &&
+ declaringType.getSuperclassName().endsWith("Activity")) //$NON-NLS-1$
+ || method.getElementName().equals("onCreate")) { //$NON-NLS-1$
+ activities.addFirst(fqcn);
+ } else {
+ activities.addLast(fqcn);
+ }
+ }
+ }
+ };
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ if (javaProject == null) {
+ return Collections.emptyList();
+ }
+ // TODO - look around a bit more and see if we can figure out whether the
+ // call if from within a setContentView call!
+
+ // Search for which java classes call setContentView(R.layout.layoutname);
+ String typeFqcn = "R.layout"; //$NON-NLS-1$
+ if (pkg != null) {
+ typeFqcn = pkg + '.' + typeFqcn;
+ }
+
+ IType type = javaProject.findType(typeFqcn);
+ if (type != null) {
+ IField field = type.getField(layoutName);
+ if (field.exists()) {
+ SearchPattern pattern = SearchPattern.createPattern(field, REFERENCES);
+ try {
+ search(requestor, javaProject, pattern);
+ } catch (OperationCanceledException canceled) {
+ // pass
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return activities;
+ }
+
+ /**
+ * Returns all activities found in the given project (including those in libraries,
+ * except for android.jar itself)
+ *
+ * @param project the project
+ * @return a list of activity classes as fully qualified class names
+ */
+ @SuppressWarnings("restriction") // BinaryType
+ @NonNull
+ public static List<String> getProjectActivities(IProject project) {
+ final List<String> activities = new ArrayList<String>();
+ try {
+ final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ if (javaProject != null) {
+ IType[] activityTypes = new IType[0];
+ IType activityType = javaProject.findType(CLASS_ACTIVITY);
+ if (activityType != null) {
+ ITypeHierarchy hierarchy =
+ activityType.newTypeHierarchy(javaProject, new NullProgressMonitor());
+ activityTypes = hierarchy.getAllSubtypes(activityType);
+ for (IType type : activityTypes) {
+ if (type instanceof BinaryType && (type.getClassFile() == null
+ || type.getClassFile().getResource() == null)) {
+ continue;
+ }
+ activities.add(type.getFullyQualifiedName());
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return activities;
+ }
+
+
+ /**
+ * Returns the activity associated with the given layout file.
+ * <p>
+ * This is an alternative to {@link #guessActivity(IFile, String)}. Whereas
+ * guessActivity simply looks for references to "R.layout.foo", this method searches
+ * for all usages of Activity#setContentView(int), and for each match it looks up the
+ * corresponding call text (such as "setContentView(R.layout.foo)"). From this it uses
+ * a regexp to pull out "foo" from this, and stores the association that layout "foo"
+ * is associated with the activity class that contained the setContentView call.
+ * <p>
+ * This has two potential advantages:
+ * <ol>
+ * <li>It can be faster. We do the reference search -once-, and we've built a map of
+ * all the layout-to-activity mappings which we can then immediately look up other
+ * layouts for, which is particularly useful at startup when we have to compute the
+ * layout activity associations to populate the theme choosers.
+ * <li>It can be more accurate. Just because an activity references an "R.layout.foo"
+ * field doesn't mean it's setting it as a content view.
+ * </ol>
+ * However, this second advantage is also its chief problem. There are some common
+ * code constructs which means that the associated layout is not explicitly referenced
+ * in a direct setContentView call; on a couple of sample projects I tested I found
+ * patterns like for example "setContentView(v)" where "v" had been computed earlier.
+ * Therefore, for now we're going to stick with the more general approach of just
+ * looking up each field when needed. We're keeping the code around, though statically
+ * compiled out with the "if (false)" construct below in case we revisit this.
+ *
+ * @param layoutFile the layout whose activity we want to look up
+ * @return the activity name
+ */
+ @SuppressWarnings("all")
+ @Nullable
+ public String guessActivityBySetContentView(String layoutName) {
+ if (false) {
+ // These should be fields
+ final Pattern LAYOUT_FIELD_PATTERN =
+ Pattern.compile("R\\.layout\\.([a-z0-9_]+)"); //$NON-NLS-1$
+ Map<String, String> mUsages = null;
+
+ sync();
+ if (mUsages == null) {
+ final Map<String, String> usages = new HashMap<String, String>();
+ mUsages = usages;
+ SearchRequestor requestor = new SearchRequestor() {
+ @Override
+ public void acceptSearchMatch(SearchMatch match) throws CoreException {
+ Object element = match.getElement();
+ if (element instanceof IMethod) {
+ IMethod method = (IMethod) element;
+ IType declaringType = method.getDeclaringType();
+ String fqcn = declaringType.getFullyQualifiedName();
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ IResource resource = match.getResource();
+ try {
+ provider.connect(resource);
+ IDocument document = provider.getDocument(resource);
+ if (document != null) {
+ String matchText = document.get(match.getOffset(),
+ match.getLength());
+ Matcher matcher = LAYOUT_FIELD_PATTERN.matcher(matchText);
+ if (matcher.find()) {
+ usages.put(matcher.group(1), fqcn);
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Can't find range information for %1$s",
+ resource.getName());
+ } finally {
+ provider.disconnect(resource);
+ }
+ }
+ }
+ };
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
+ if (javaProject == null) {
+ return null;
+ }
+
+ // Search for which java classes call setContentView(R.layout.layoutname);
+ String typeFqcn = "R.layout"; //$NON-NLS-1$
+ if (mPackage != null) {
+ typeFqcn = mPackage + '.' + typeFqcn;
+ }
+
+ IType activityType = javaProject.findType(CLASS_ACTIVITY);
+ if (activityType != null) {
+ IMethod method = activityType.getMethod(
+ "setContentView", new String[] {"I"}); //$NON-NLS-1$ //$NON-NLS-2$
+ if (method.exists()) {
+ SearchPattern pattern = SearchPattern.createPattern(method,
+ REFERENCES);
+ search(requestor, javaProject, pattern);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ return mUsages.get(layoutName);
+ }
+
+ return null;
+ }
+
+ /**
+ * Performs a search using the given pattern, scope and handler. The search will abort
+ * if it takes longer than {@link #SEARCH_TIMEOUT_MS} milliseconds.
+ */
+ private static void search(SearchRequestor requestor, IJavaProject javaProject,
+ SearchPattern pattern) throws CoreException {
+ // Find the package fragment specified in the manifest; the activities should
+ // live there.
+ IJavaSearchScope scope = createPackageScope(javaProject);
+
+ SearchParticipant[] participants = new SearchParticipant[] {
+ SearchEngine.getDefaultSearchParticipant()
+ };
+ SearchEngine engine = new SearchEngine();
+
+ final long searchStart = System.currentTimeMillis();
+ NullProgressMonitor monitor = new NullProgressMonitor() {
+ private boolean mCancelled;
+ @Override
+ public void internalWorked(double work) {
+ long searchEnd = System.currentTimeMillis();
+ if (searchEnd - searchStart > SEARCH_TIMEOUT_MS) {
+ mCancelled = true;
+ }
+ }
+
+ @Override
+ public boolean isCanceled() {
+ return mCancelled;
+ }
+ };
+ engine.search(pattern, participants, scope, requestor, monitor);
+ }
+
+ /** Creates a package search scope for the first package root in the given java project */
+ private static IJavaSearchScope createPackageScope(IJavaProject javaProject) {
+ IPackageFragmentRoot packageRoot = getSourcePackageRoot(javaProject);
+
+ IJavaSearchScope scope;
+ if (packageRoot != null) {
+ IJavaElement[] scopeElements = new IJavaElement[] { packageRoot };
+ scope = SearchEngine.createJavaSearchScope(scopeElements);
+ } else {
+ scope = SearchEngine.createWorkspaceScope();
+ }
+ return scope;
+ }
+
+ /**
+ * Returns the first package root for the given java project
+ *
+ * @param javaProject the project to search in
+ * @return the first package root, or null
+ */
+ @Nullable
+ public static IPackageFragmentRoot getSourcePackageRoot(IJavaProject javaProject) {
+ IPackageFragmentRoot packageRoot = null;
+ List<IPath> sources = BaseProjectHelper.getSourceClasspaths(javaProject);
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ for (IPath path : sources) {
+ IResource firstSource = workspace.getRoot().findMember(path);
+ if (firstSource != null) {
+ packageRoot = javaProject.getPackageFragmentRoot(firstSource);
+ if (packageRoot != null) {
+ break;
+ }
+ }
+ }
+ return packageRoot;
+ }
+
+ /**
+ * 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;
+ }
+ }
+ }
+
+ value = AndroidManifest.getTargetSdkVersion(manifestFile);
+ if (value == null) {
+ mTargetSdkVersion = mMinSdkVersion;
+ } 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
+ mTargetSdkVersion = target.getVersion().getApiLevel() + 1;
+ }
+ }
+ }
+ } 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);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestSourceViewerConfig.java
new file mode 100644
index 000000000..a3d398657
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestSourceViewerConfig.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest;
+
+
+import com.android.ide.eclipse.adt.internal.editors.AndroidSourceViewerConfig;
+
+import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
+import org.eclipse.jface.text.source.ISourceViewer;
+
+/**
+ * Source Viewer Configuration that calls in ManifestContentAssist.
+ */
+public final class ManifestSourceViewerConfig extends AndroidSourceViewerConfig {
+
+ private ManifestContentAssist mAndroidContentAssist;
+
+ public ManifestSourceViewerConfig() {
+ super();
+ mAndroidContentAssist = new ManifestContentAssist();
+ }
+
+ @Override
+ public IContentAssistProcessor getAndroidContentAssistProcessor(
+ ISourceViewer sourceViewer,
+ String partitionType) {
+ return mAndroidContentAssist;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java
new file mode 100644
index 000000000..3429e43a0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.descriptors;
+
+import com.android.SdkConstants;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.common.resources.platform.AttrsXmlParser;
+import com.android.ide.common.resources.platform.DeclareStyleableInfo;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor.Mandatory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ITextAttributeCreator;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ListAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
+
+import org.eclipse.core.runtime.IStatus;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+
+
+/**
+ * Complete description of the AndroidManifest.xml structure.
+ * <p/>
+ * The root element are static instances which always exists.
+ * However their sub-elements and attributes are created only when the SDK changes or is
+ * loaded the first time.
+ */
+public final class AndroidManifestDescriptors implements IDescriptorProvider {
+ /** Name of the {@code <uses-permission>} */
+ public static final String USES_PERMISSION = "uses-permission"; //$NON-NLS-1$
+ private static final String MANIFEST_NODE_NAME = "manifest"; //$NON-NLS-1$
+ private static final String ANDROID_MANIFEST_STYLEABLE =
+ AttrsXmlParser.ANDROID_MANIFEST_STYLEABLE;
+
+ // Public attributes names, attributes descriptors and elements descriptors
+
+ public static final String ANDROID_LABEL_ATTR = "label"; //$NON-NLS-1$
+ public static final String ANDROID_NAME_ATTR = "name"; //$NON-NLS-1$
+ public static final String PACKAGE_ATTR = "package"; //$NON-NLS-1$
+
+ /** The {@link ElementDescriptor} for the root Manifest element. */
+ private final ElementDescriptor MANIFEST_ELEMENT;
+ /** The {@link ElementDescriptor} for the root Application element. */
+ private final ElementDescriptor APPLICATION_ELEMENT;
+
+ /** The {@link ElementDescriptor} for the root Instrumentation element. */
+ private final ElementDescriptor INTRUMENTATION_ELEMENT;
+ /** The {@link ElementDescriptor} for the root Permission element. */
+ private final ElementDescriptor PERMISSION_ELEMENT;
+ /** The {@link ElementDescriptor} for the root UsesPermission element. */
+ private final ElementDescriptor USES_PERMISSION_ELEMENT;
+ /** The {@link ElementDescriptor} for the root UsesSdk element. */
+ private final ElementDescriptor USES_SDK_ELEMENT;
+
+ /** The {@link ElementDescriptor} for the root PermissionGroup element. */
+ private final ElementDescriptor PERMISSION_GROUP_ELEMENT;
+ /** The {@link ElementDescriptor} for the root PermissionTree element. */
+ private final ElementDescriptor PERMISSION_TREE_ELEMENT;
+
+ /** Private package attribute for the manifest element. Needs to be handled manually. */
+ private final TextAttributeDescriptor PACKAGE_ATTR_DESC;
+
+ public AndroidManifestDescriptors() {
+ APPLICATION_ELEMENT = createElement("application", null, Mandatory.MANDATORY_LAST); //$NON-NLS-1$ + no child & mandatory
+ INTRUMENTATION_ELEMENT = createElement("instrumentation"); //$NON-NLS-1$
+
+ PERMISSION_ELEMENT = createElement("permission"); //$NON-NLS-1$
+ USES_PERMISSION_ELEMENT = createElement(USES_PERMISSION);
+ USES_SDK_ELEMENT = createElement("uses-sdk", null, Mandatory.MANDATORY); //$NON-NLS-1$ + no child & mandatory
+
+ PERMISSION_GROUP_ELEMENT = createElement("permission-group"); //$NON-NLS-1$
+ PERMISSION_TREE_ELEMENT = createElement("permission-tree"); //$NON-NLS-1$
+
+ MANIFEST_ELEMENT = createElement(
+ MANIFEST_NODE_NAME, // xml name
+ new ElementDescriptor[] {
+ APPLICATION_ELEMENT,
+ INTRUMENTATION_ELEMENT,
+ PERMISSION_ELEMENT,
+ USES_PERMISSION_ELEMENT,
+ PERMISSION_GROUP_ELEMENT,
+ PERMISSION_TREE_ELEMENT,
+ USES_SDK_ELEMENT,
+ },
+ Mandatory.MANDATORY);
+
+ // The "package" attribute is treated differently as it doesn't have the standard
+ // Android XML namespace.
+ PACKAGE_ATTR_DESC = new PackageAttributeDescriptor(PACKAGE_ATTR,
+ null /* nsUri */,
+ new AttributeInfo(PACKAGE_ATTR, Format.REFERENCE_SET)).setTooltip(
+ "This attribute gives a unique name for the package, using a Java-style " +
+ "naming convention to avoid name collisions.\nFor example, applications " +
+ "published by Google could have names of the form com.google.app.appname");
+ }
+
+ @Override
+ public ElementDescriptor[] getRootElementDescriptors() {
+ return new ElementDescriptor[] { MANIFEST_ELEMENT };
+ }
+
+ @Override
+ public ElementDescriptor getDescriptor() {
+ return getManifestElement();
+ }
+
+ public ElementDescriptor getApplicationElement() {
+ return APPLICATION_ELEMENT;
+ }
+
+ public ElementDescriptor getManifestElement() {
+ return MANIFEST_ELEMENT;
+ }
+
+ public ElementDescriptor getUsesSdkElement() {
+ return USES_SDK_ELEMENT;
+ }
+
+ public ElementDescriptor getInstrumentationElement() {
+ return INTRUMENTATION_ELEMENT;
+ }
+
+ public ElementDescriptor getPermissionElement() {
+ return PERMISSION_ELEMENT;
+ }
+
+ public ElementDescriptor getUsesPermissionElement() {
+ return USES_PERMISSION_ELEMENT;
+ }
+
+ public ElementDescriptor getPermissionGroupElement() {
+ return PERMISSION_GROUP_ELEMENT;
+ }
+
+ public ElementDescriptor getPermissionTreeElement() {
+ return PERMISSION_TREE_ELEMENT;
+ }
+
+ /**
+ * Updates the document descriptor.
+ * <p/>
+ * It first computes the new children of the descriptor and then updates them
+ * all at once.
+ *
+ * @param manifestMap The map style => attributes from the attrs_manifest.xml file
+ */
+ public synchronized void updateDescriptors(
+ Map<String, DeclareStyleableInfo> manifestMap) {
+
+ // -- setup the required attributes overrides --
+
+ Set<String> required = new HashSet<String>();
+ required.add("provider/authorities"); //$NON-NLS-1$
+
+ // -- setup the various attribute format overrides --
+
+ // The key for each override is "element1,element2,.../attr-xml-local-name" or
+ // "*/attr-xml-local-name" to match the attribute in any element.
+
+ Map<String, ITextAttributeCreator> overrides = new HashMap<String, ITextAttributeCreator>();
+
+ overrides.put("*/icon", ReferenceAttributeDescriptor.CREATOR); //$NON-NLS-1$
+
+ overrides.put("*/theme", ThemeAttributeDescriptor.CREATOR); //$NON-NLS-1$
+ overrides.put("*/permission", ListAttributeDescriptor.CREATOR); //$NON-NLS-1$
+ overrides.put("*/targetPackage", ManifestPkgAttrDescriptor.CREATOR); //$NON-NLS-1$
+
+ overrides.put("uses-library/name", ListAttributeDescriptor.CREATOR); //$NON-NLS-1$
+ overrides.put("action,category,uses-permission/" + ANDROID_NAME_ATTR, //$NON-NLS-1$
+ ListAttributeDescriptor.CREATOR);
+
+ overrideClassName(overrides, "application", //$NON-NLS-1$
+ SdkConstants.CLASS_APPLICATION,
+ false /*mandatory*/);
+ overrideClassName(overrides, "application/backupAgent", //$NON-NLS-1$
+ "android.app.backup.BackupAgent", //$NON-NLS-1$
+ false /*mandatory*/);
+ overrideClassName(overrides, "activity", SdkConstants.CLASS_ACTIVITY); //$NON-NLS-1$
+ overrideClassName(overrides, "receiver", SdkConstants.CLASS_BROADCASTRECEIVER);//$NON-NLS-1$
+ overrideClassName(overrides, "service", SdkConstants.CLASS_SERVICE); //$NON-NLS-1$
+ overrideClassName(overrides, "provider", SdkConstants.CLASS_CONTENTPROVIDER); //$NON-NLS-1$
+ overrideClassName(overrides, "instrumentation",
+ SdkConstants.CLASS_INSTRUMENTATION); //$NON-NLS-1$
+
+ // -- list element nodes already created --
+ // These elements are referenced by already opened editors, so we want to update them
+ // but not re-create them when reloading an SDK on the fly.
+
+ HashMap<String, ElementDescriptor> elementDescs =
+ new HashMap<String, ElementDescriptor>();
+ elementDescs.put(MANIFEST_ELEMENT.getXmlLocalName(), MANIFEST_ELEMENT);
+ elementDescs.put(APPLICATION_ELEMENT.getXmlLocalName(), APPLICATION_ELEMENT);
+ elementDescs.put(INTRUMENTATION_ELEMENT.getXmlLocalName(), INTRUMENTATION_ELEMENT);
+ elementDescs.put(PERMISSION_ELEMENT.getXmlLocalName(), PERMISSION_ELEMENT);
+ elementDescs.put(USES_PERMISSION_ELEMENT.getXmlLocalName(), USES_PERMISSION_ELEMENT);
+ elementDescs.put(USES_SDK_ELEMENT.getXmlLocalName(), USES_SDK_ELEMENT);
+ elementDescs.put(PERMISSION_GROUP_ELEMENT.getXmlLocalName(), PERMISSION_GROUP_ELEMENT);
+ elementDescs.put(PERMISSION_TREE_ELEMENT.getXmlLocalName(), PERMISSION_TREE_ELEMENT);
+
+ // --
+
+ inflateElement(manifestMap,
+ overrides,
+ required,
+ elementDescs,
+ MANIFEST_ELEMENT,
+ "AndroidManifest"); //$NON-NLS-1$
+ insertAttribute(MANIFEST_ELEMENT, PACKAGE_ATTR_DESC);
+
+ XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(
+ SdkConstants.ANDROID_NS_NAME, SdkConstants.ANDROID_URI);
+ insertAttribute(MANIFEST_ELEMENT, xmlns);
+
+ /*
+ *
+ *
+ */
+ assert sanityCheck(manifestMap, MANIFEST_ELEMENT);
+ }
+
+ /**
+ * Sets up a mandatory attribute override using a ClassAttributeDescriptor
+ * with the specified class name.
+ *
+ * @param overrides The current map of overrides.
+ * @param elementName The element name to override, e.g. "application".
+ * If this name does NOT have a slash (/), the ANDROID_NAME_ATTR attribute will be overriden.
+ * Otherwise, if it contains a (/) the format is "element/attribute", for example
+ * "application/name" vs "application/backupAgent".
+ * @param className The fully qualified name of the base class of the attribute.
+ */
+ private static void overrideClassName(
+ Map<String, ITextAttributeCreator> overrides,
+ String elementName,
+ final String className) {
+ overrideClassName(overrides, elementName, className, true);
+ }
+
+ /**
+ * Sets up an attribute override using a ClassAttributeDescriptor
+ * with the specified class name.
+ *
+ * @param overrides The current map of overrides.
+ * @param elementName The element name to override, e.g. "application".
+ * If this name does NOT have a slash (/), the ANDROID_NAME_ATTR attribute will be overriden.
+ * Otherwise, if it contains a (/) the format is "element/attribute", for example
+ * "application/name" vs "application/backupAgent".
+ * @param className The fully qualified name of the base class of the attribute.
+ * @param mandatory True if this attribute is mandatory, false if optional.
+ */
+ private static void overrideClassName(
+ Map<String, ITextAttributeCreator> overrides,
+ String elementName,
+ final String className,
+ final boolean mandatory) {
+ if (elementName.indexOf('/') == -1) {
+ elementName = elementName + '/' + ANDROID_NAME_ATTR;
+ }
+ overrides.put(elementName,
+ new ITextAttributeCreator() {
+ @Override
+ public TextAttributeDescriptor create(String xmlName, String nsUri,
+ IAttributeInfo attrInfo) {
+ if (attrInfo == null) {
+ attrInfo = new AttributeInfo(xmlName, Format.STRING_SET );
+ }
+
+ if (SdkConstants.CLASS_ACTIVITY.equals(className)) {
+ return new ClassAttributeDescriptor(
+ className,
+ PostActivityCreationAction.getAction(),
+ xmlName,
+ nsUri,
+ attrInfo,
+ mandatory,
+ true /*defaultToProjectOnly*/);
+ } else if (SdkConstants.CLASS_BROADCASTRECEIVER.equals(className)) {
+ return new ClassAttributeDescriptor(
+ className,
+ PostReceiverCreationAction.getAction(),
+ xmlName,
+ nsUri,
+ attrInfo,
+ mandatory,
+ true /*defaultToProjectOnly*/);
+ } else if (SdkConstants.CLASS_INSTRUMENTATION.equals(className)) {
+ return new ClassAttributeDescriptor(
+ className,
+ null, // no post action
+ xmlName,
+ nsUri,
+ attrInfo,
+ mandatory,
+ false /*defaultToProjectOnly*/);
+ } else {
+ return new ClassAttributeDescriptor(
+ className,
+ xmlName,
+ nsUri,
+ attrInfo,
+ mandatory);
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns a new ElementDescriptor constructed from the information given here
+ * and the javadoc & attributes extracted from the style map if any.
+ * <p/>
+ * Creates an element with no attribute overrides.
+ */
+ private ElementDescriptor createElement(
+ String xmlName,
+ ElementDescriptor[] childrenElements,
+ Mandatory mandatory) {
+ // Creates an element with no attribute overrides.
+ String styleName = guessStyleName(xmlName);
+ String sdkUrl = DescriptorsUtils.MANIFEST_SDK_URL + styleName;
+ String uiName = getUiName(xmlName);
+
+ ElementDescriptor element = new ManifestElementDescriptor(xmlName, uiName, null, sdkUrl,
+ null, childrenElements, mandatory);
+
+ return element;
+ }
+
+ /**
+ * Returns a new ElementDescriptor constructed from its XML local name.
+ * <p/>
+ * This version creates an element not mandatory.
+ */
+ private ElementDescriptor createElement(String xmlName) {
+ // Creates an element with no child and not mandatory
+ return createElement(xmlName, null, Mandatory.NOT_MANDATORY);
+ }
+
+ /**
+ * Inserts an attribute in this element attribute list if it is not present there yet
+ * (based on the attribute XML name.)
+ * The attribute is inserted at the beginning of the attribute list.
+ */
+ private void insertAttribute(ElementDescriptor element, AttributeDescriptor newAttr) {
+ AttributeDescriptor[] attributes = element.getAttributes();
+ for (AttributeDescriptor attr : attributes) {
+ if (attr.getXmlLocalName().equals(newAttr.getXmlLocalName())) {
+ return;
+ }
+ }
+
+ AttributeDescriptor[] newArray = new AttributeDescriptor[attributes.length + 1];
+ newArray[0] = newAttr;
+ System.arraycopy(attributes, 0, newArray, 1, attributes.length);
+ element.setAttributes(newArray);
+ }
+
+ /**
+ * "Inflates" the properties of an {@link ElementDescriptor} from the styleable declaration.
+ * <p/>
+ * This first creates all the attributes for the given ElementDescriptor.
+ * It then finds all children of the descriptor, inflates them recursively and sets them
+ * as child to this ElementDescriptor.
+ *
+ * @param styleMap The input styleable map for manifest elements & attributes.
+ * @param overrides A list of attribute overrides (to customize the type of the attribute
+ * descriptors).
+ * @param requiredAttributes Set of attributes to be marked as required.
+ * @param existingElementDescs A map of already created element descriptors, keyed by
+ * XML local name. This is used to use the static elements created initially by this
+ * class, which are referenced directly by editors (so that reloading an SDK won't
+ * break these references).
+ * @param elemDesc The current {@link ElementDescriptor} to inflate.
+ * @param styleName The name of the {@link ElementDescriptor} to inflate. Its XML local name
+ * will be guessed automatically from the style name.
+ */
+ private void inflateElement(
+ Map<String, DeclareStyleableInfo> styleMap,
+ Map<String, ITextAttributeCreator> overrides,
+ Set<String> requiredAttributes,
+ HashMap<String, ElementDescriptor> existingElementDescs,
+ ElementDescriptor elemDesc,
+ String styleName) {
+ assert elemDesc != null;
+ assert styleName != null;
+ assert styleMap != null;
+
+ if (styleMap == null) {
+ return;
+ }
+
+ // define attributes
+ DeclareStyleableInfo style = styleMap.get(styleName);
+ if (style != null) {
+ ArrayList<AttributeDescriptor> attrDescs = new ArrayList<AttributeDescriptor>();
+ DescriptorsUtils.appendAttributes(attrDescs,
+ elemDesc.getXmlLocalName(),
+ SdkConstants.NS_RESOURCES,
+ style.getAttributes(),
+ requiredAttributes,
+ overrides);
+ elemDesc.setTooltip(style.getJavaDoc());
+ elemDesc.setAttributes(attrDescs.toArray(new AttributeDescriptor[attrDescs.size()]));
+ }
+
+ // find all elements that have this one as parent
+ ArrayList<ElementDescriptor> children = new ArrayList<ElementDescriptor>();
+ for (Entry<String, DeclareStyleableInfo> entry : styleMap.entrySet()) {
+ DeclareStyleableInfo childStyle = entry.getValue();
+ boolean isParent = false;
+ String[] parents = childStyle.getParents();
+ if (parents != null) {
+ for (String parent: parents) {
+ if (styleName.equals(parent)) {
+ isParent = true;
+ break;
+ }
+ }
+ }
+ if (isParent) {
+ String childStyleName = entry.getKey();
+ String childXmlName = guessXmlName(childStyleName);
+
+ // create or re-use element
+ ElementDescriptor child = existingElementDescs.get(childXmlName);
+ if (child == null) {
+ child = createElement(childXmlName);
+ existingElementDescs.put(childXmlName, child);
+ }
+ children.add(child);
+
+ inflateElement(styleMap,
+ overrides,
+ requiredAttributes,
+ existingElementDescs,
+ child,
+ childStyleName);
+ }
+ }
+ elemDesc.setChildren(children.toArray(new ElementDescriptor[children.size()]));
+ }
+
+ /**
+ * Get an UI name from the element XML name.
+ * <p/>
+ * Capitalizes the first letter and replace non-alphabet by a space followed by a capital.
+ */
+ private static String getUiName(String xmlName) {
+ StringBuilder sb = new StringBuilder();
+
+ boolean capitalize = true;
+ for (char c : xmlName.toCharArray()) {
+ if (capitalize && c >= 'a' && c <= 'z') {
+ sb.append((char)(c + 'A' - 'a'));
+ capitalize = false;
+ } else if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
+ sb.append(' ');
+ capitalize = true;
+ } else {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Guesses the style name for a given XML element name.
+ * <p/>
+ * The rules are:
+ * - capitalize the first letter:
+ * - if there's a dash, skip it and capitalize the next one
+ * - prefix AndroidManifest
+ * The exception is "manifest" which just becomes AndroidManifest.
+ * <p/>
+ * Examples:
+ * - manifest => AndroidManifest
+ * - application => AndroidManifestApplication
+ * - uses-permission => AndroidManifestUsesPermission
+ */
+ private String guessStyleName(String xmlName) {
+ StringBuilder sb = new StringBuilder();
+
+ if (!xmlName.equals(MANIFEST_NODE_NAME)) {
+ boolean capitalize = true;
+ for (char c : xmlName.toCharArray()) {
+ if (capitalize && c >= 'a' && c <= 'z') {
+ sb.append((char)(c + 'A' - 'a'));
+ capitalize = false;
+ } else if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
+ // not a letter -- skip the character and capitalize the next one
+ capitalize = true;
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+
+ sb.insert(0, ANDROID_MANIFEST_STYLEABLE);
+ return sb.toString();
+ }
+
+ /**
+ * This method performs a sanity check to make sure all the styles declared in the
+ * manifestMap are actually defined in the actual element descriptors and reachable from
+ * the manifestElement root node.
+ */
+ private boolean sanityCheck(Map<String, DeclareStyleableInfo> manifestMap,
+ ElementDescriptor manifestElement) {
+ TreeSet<String> elementsDeclared = new TreeSet<String>();
+ findAllElementNames(manifestElement, elementsDeclared);
+
+ TreeSet<String> stylesDeclared = new TreeSet<String>();
+ for (String styleName : manifestMap.keySet()) {
+ if (styleName.startsWith(ANDROID_MANIFEST_STYLEABLE)) {
+ stylesDeclared.add(styleName);
+ }
+ }
+
+ for (Iterator<String> it = elementsDeclared.iterator(); it.hasNext();) {
+ String xmlName = it.next();
+ String styleName = guessStyleName(xmlName);
+ if (stylesDeclared.remove(styleName)) {
+ it.remove();
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ if (!stylesDeclared.isEmpty()) {
+ sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by the SDK but unknown to ADT: ");
+ for (String name : stylesDeclared) {
+ sb.append(guessXmlName(name));
+
+ if (!name.equals(stylesDeclared.last())) {
+ sb.append(", "); //$NON-NLS-1$
+ }
+ }
+
+ AdtPlugin.log(IStatus.WARNING, "%s", sb.toString());
+ AdtPlugin.printToConsole((String)null, sb);
+ sb.setLength(0);
+ }
+
+ if (!elementsDeclared.isEmpty()) {
+ sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by ADT but not by the SDK: ");
+ for (String name : elementsDeclared) {
+ sb.append(name);
+ if (!name.equals(elementsDeclared.last())) {
+ sb.append(", "); //$NON-NLS-1$
+ }
+ }
+
+ AdtPlugin.log(IStatus.WARNING, "%s", sb.toString());
+ AdtPlugin.printToConsole((String)null, sb);
+ }
+
+ return true;
+ }
+
+ /**
+ * Performs an approximate translation of the style name into a potential
+ * xml name. This is more or less the reverse from guessStyleName().
+ *
+ * @return The XML local name for a given style name.
+ */
+ private String guessXmlName(String name) {
+ StringBuilder sb = new StringBuilder();
+ if (ANDROID_MANIFEST_STYLEABLE.equals(name)) {
+ sb.append(MANIFEST_NODE_NAME);
+ } else {
+ name = name.replace(ANDROID_MANIFEST_STYLEABLE, ""); //$NON-NLS-1$
+ boolean first_char = true;
+ for (char c : name.toCharArray()) {
+ if (c >= 'A' && c <= 'Z') {
+ if (!first_char) {
+ sb.append('-');
+ }
+ c = (char) (c - 'A' + 'a');
+ }
+ sb.append(c);
+ first_char = false;
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Helper method used by {@link #sanityCheck(Map, ElementDescriptor)} to find all the
+ * {@link ElementDescriptor} names defined by the tree of descriptors.
+ * <p/>
+ * Note: this assumes no circular reference in the tree of {@link ElementDescriptor}s.
+ */
+ private void findAllElementNames(ElementDescriptor element, TreeSet<String> declared) {
+ declared.add(element.getXmlName());
+ for (ElementDescriptor desc : element.getChildren()) {
+ findAllElementNames(desc, declared);
+ }
+ }
+
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ClassAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ClassAttributeDescriptor.java
new file mode 100644
index 000000000..1a27f8d00
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ClassAttributeDescriptor.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.descriptors;
+
+import com.android.SdkConstants;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.model.UiClassAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+/**
+ * Describes an XML attribute representing a class name.
+ * It is displayed by a {@link UiClassAttributeNode}.
+ */
+public class ClassAttributeDescriptor extends TextAttributeDescriptor {
+
+ /** Superclass of the class value. */
+ private String mSuperClassName;
+
+ private IPostTypeCreationAction mPostCreationAction;
+
+ /** indicates if the class parameter is mandatory */
+ boolean mMandatory;
+
+ private final boolean mDefaultToProjectOnly;
+
+ /**
+ * Creates a new {@link ClassAttributeDescriptor}
+ * @param superClassName the fully qualified name of the superclass of the class represented
+ * by the attribute.
+ * @param xmlLocalName The XML name of the attribute (case sensitive, with android: prefix).
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param attrInfo The {@link IAttributeInfo} of this attribute. Can't be null.
+ * @param mandatory indicates if the class attribute is mandatory.
+ */
+ public ClassAttributeDescriptor(String superClassName,
+ String xmlLocalName,
+ String nsUri,
+ IAttributeInfo attrInfo,
+ boolean mandatory) {
+ super(xmlLocalName, nsUri, attrInfo);
+ mSuperClassName = superClassName;
+ mDefaultToProjectOnly = true;
+ if (mandatory) {
+ mMandatory = true;
+ setRequired(true);
+ }
+ }
+
+ /**
+ * Creates a new {@link ClassAttributeDescriptor}
+ * @param superClassName the fully qualified name of the superclass of the class represented
+ * by the attribute.
+ * @param postCreationAction the {@link IPostTypeCreationAction} to be executed on the
+ * newly created class.
+ * @param xmlLocalName The XML local name of the attribute (case sensitive).
+ * @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
+ * @param attrInfo The {@link IAttributeInfo} of this attribute. Can't be null.
+ * @param mandatory indicates if the class attribute is mandatory.
+ * @param defaultToProjectOnly True if only classes from the sources of this project should
+ * be shown by default in the class browser.
+ */
+ public ClassAttributeDescriptor(String superClassName,
+ IPostTypeCreationAction postCreationAction,
+ String xmlLocalName,
+ String nsUri,
+ IAttributeInfo attrInfo,
+ boolean mandatory,
+ boolean defaultToProjectOnly) {
+ super(xmlLocalName, nsUri, attrInfo);
+ mSuperClassName = superClassName;
+ mPostCreationAction = postCreationAction;
+ mDefaultToProjectOnly = defaultToProjectOnly;
+ if (mandatory) {
+ mMandatory = true;
+ setRequired(true);
+ }
+ }
+
+ /**
+ * @return A new {@link UiClassAttributeNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiClassAttributeNode(mSuperClassName, mPostCreationAction,
+ mMandatory, this, uiParent, mDefaultToProjectOnly);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestElementDescriptor.java
new file mode 100644
index 000000000..deb815e57
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestElementDescriptor.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.descriptors;
+
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.model.UiManifestElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+/**
+ * {@link ManifestElementDescriptor} describes an XML element node, with its
+ * element name, its possible attributes, its possible child elements but also
+ * its display name and tooltip.
+ *
+ * This {@link ElementDescriptor} is specialized to create {@link UiManifestElementNode} UI nodes.
+ */
+public class ManifestElementDescriptor extends ElementDescriptor {
+
+ /**
+ * Constructs a new {@link ManifestElementDescriptor}.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ * @param ui_name The XML element name for the user interface, typically capitalized.
+ * @param tooltip An optional tooltip. Can be null or empty.
+ * @param sdk_url An optional SKD URL. Can be null or empty.
+ * @param attributes The list of allowed attributes. Can be null or empty.
+ * @param children The list of allowed children. Can be null or empty.
+ * @param mandatory Whether this node must always exist (even for empty models).
+ */
+ public ManifestElementDescriptor(String xml_name,
+ String ui_name,
+ String tooltip,
+ String sdk_url,
+ AttributeDescriptor[] attributes,
+ ElementDescriptor[] children,
+ Mandatory mandatory) {
+ super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory);
+ }
+
+ /**
+ * Constructs a new {@link ManifestElementDescriptor}.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ * @param ui_name The XML element name for the user interface, typically capitalized.
+ * @param tooltip An optional tooltip. Can be null or empty.
+ * @param sdk_url An optional SKD URL. Can be null or empty.
+ * @param attributes The list of allowed attributes. Can be null or empty.
+ * @param children The list of allowed children. Can be null or empty.
+ * @param mandatory Whether this node must always exist (even for empty models).
+ */
+ public ManifestElementDescriptor(String xml_name,
+ String ui_name,
+ String tooltip,
+ String sdk_url,
+ AttributeDescriptor[] attributes,
+ ElementDescriptor[] children,
+ boolean mandatory) {
+ super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory);
+ }
+
+ /**
+ * Constructs a new {@link ManifestElementDescriptor}.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ * @param ui_name The XML element name for the user interface, typically capitalized.
+ * @param tooltip An optional tooltip. Can be null or empty.
+ * @param sdk_url An optional SKD URL. Can be null or empty.
+ * @param attributes The list of allowed attributes. Can be null or empty.
+ * @param children The list of allowed children. Can be null or empty.
+ */
+ public ManifestElementDescriptor(String xml_name,
+ String ui_name,
+ String tooltip,
+ String sdk_url,
+ AttributeDescriptor[] attributes,
+ ElementDescriptor[] children) {
+ super(xml_name, ui_name, tooltip, sdk_url, attributes, children, false);
+ }
+
+ /**
+ * This is a shortcut for
+ * ManifestElementDescriptor(xml_name, xml_name.capitalize(), null, null, null, children).
+ * This constructor is mostly used for unit tests.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ */
+ public ManifestElementDescriptor(String xml_name, ElementDescriptor[] children) {
+ super(xml_name, children);
+ }
+
+ /**
+ * This is a shortcut for
+ * ManifestElementDescriptor(xml_name, xml_name.capitalize(), null, null, null, null).
+ * This constructor is mostly used for unit tests.
+ *
+ * @param xml_name The XML element node name. Case sensitive.
+ */
+ public ManifestElementDescriptor(String xml_name) {
+ super(xml_name, null);
+ }
+
+ /**
+ * @return A new {@link UiElementNode} linked to this descriptor.
+ */
+ @Override
+ public UiElementNode createUiNode() {
+ return new UiManifestElementNode(this);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestPkgAttrDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestPkgAttrDescriptor.java
new file mode 100755
index 000000000..74b789487
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ManifestPkgAttrDescriptor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.descriptors;
+
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ITextAttributeCreator;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.model.UiManifestPkgAttrNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+/**
+ * Describes a package XML attribute. It is displayed by a {@link UiManifestPkgAttrNode}.
+ * <p/>
+ * Used by the override for .../targetPackage in {@link AndroidManifestDescriptors}.
+ */
+public class ManifestPkgAttrDescriptor extends TextAttributeDescriptor {
+
+ /**
+ * Used by {@link DescriptorsUtils} to create instances of this descriptor.
+ */
+ public static final ITextAttributeCreator CREATOR = new ITextAttributeCreator() {
+ @Override
+ public TextAttributeDescriptor create(String xmlLocalName,
+ String nsUri, IAttributeInfo attrInfo) {
+ return new ManifestPkgAttrDescriptor(xmlLocalName, nsUri, attrInfo);
+ }
+ };
+
+ public ManifestPkgAttrDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ }
+
+ /**
+ * @return A new {@link UiManifestPkgAttrNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiManifestPkgAttrNode(this, uiParent);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PackageAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PackageAttributeDescriptor.java
new file mode 100644
index 000000000..e8395ac40
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PackageAttributeDescriptor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.descriptors;
+
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.model.UiPackageAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+/**
+ * Describes a package XML attribute. It is displayed by a {@link UiPackageAttributeNode}.
+ */
+public class PackageAttributeDescriptor extends TextAttributeDescriptor {
+
+ public PackageAttributeDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ }
+
+ /**
+ * @return A new {@link UiPackageAttributeNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiPackageAttributeNode(this, uiParent);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PostActivityCreationAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PostActivityCreationAction.java
new file mode 100644
index 000000000..60c663dfc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PostActivityCreationAction.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.descriptors;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.internal.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction;
+
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+
+/**
+ * Action to be executed after an Activity class is created.
+ */
+class PostActivityCreationAction implements IPostTypeCreationAction {
+
+ private final static PostActivityCreationAction sAction = new PostActivityCreationAction();
+
+ private PostActivityCreationAction() {
+ // private constructor to enforce singleton.
+ }
+
+
+ /**
+ * Returns the action.
+ */
+ public static IPostTypeCreationAction getAction() {
+ return sAction;
+ }
+
+ /**
+ * Processes a newly created Activity.
+ *
+ */
+ @Override
+ public void processNewType(IType newType) {
+ try {
+ String methodContent =
+ " /** Called when the activity is first created. */\n" +
+ " @Override\n" +
+ " public void onCreate(Bundle savedInstanceState) {\n" +
+ " super.onCreate(savedInstanceState);\n" +
+ "\n" +
+ " // TODO Auto-generated method stub\n" +
+ " }";
+ newType.createMethod(methodContent, null /* sibling*/, false /* force */,
+ new NullProgressMonitor());
+
+ // we need to add the import for Bundle, so we need the compilation unit.
+ // Since the type could be enclosed in other types, we loop till we find it.
+ ICompilationUnit compilationUnit = null;
+ IJavaElement element = newType;
+ do {
+ IJavaElement parentElement = element.getParent();
+ if (parentElement != null) {
+ if (parentElement.getElementType() == IJavaElement.COMPILATION_UNIT) {
+ compilationUnit = (ICompilationUnit)parentElement;
+ }
+
+ element = parentElement;
+ } else {
+ break;
+ }
+ } while (compilationUnit == null);
+
+ if (compilationUnit != null) {
+ compilationUnit.createImport(SdkConstants.CLASS_BUNDLE,
+ null /* sibling */, new NullProgressMonitor());
+ }
+ } catch (JavaModelException e) {
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PostReceiverCreationAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PostReceiverCreationAction.java
new file mode 100644
index 000000000..8b2c36144
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/PostReceiverCreationAction.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.descriptors;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.internal.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction;
+
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+
+/**
+ * Action to be executed after an BroadcastReceiver class is created.
+ */
+class PostReceiverCreationAction implements IPostTypeCreationAction {
+
+ private final static PostReceiverCreationAction sAction = new PostReceiverCreationAction();
+
+ private PostReceiverCreationAction() {
+ // private constructor to enforce singleton.
+ }
+
+ /**
+ * Returns the action.
+ */
+ public static IPostTypeCreationAction getAction() {
+ return sAction;
+ }
+
+ /**
+ * Processes a newly created Activity.
+ *
+ */
+ @Override
+ public void processNewType(IType newType) {
+ try {
+ String methodContent =
+ " @Override\n" +
+ " public void onReceive(Context context, Intent intent) {\n" +
+ " // TODO Auto-generated method stub\n" +
+ " }";
+ newType.createMethod(methodContent, null /* sibling*/, false /* force */,
+ new NullProgressMonitor());
+
+ // we need to add the import for Bundle, so we need the compilation unit.
+ // Since the type could be enclosed in other types, we loop till we find it.
+ ICompilationUnit compilationUnit = null;
+ IJavaElement element = newType;
+ do {
+ IJavaElement parentElement = element.getParent();
+ if (parentElement != null) {
+ if (parentElement.getElementType() == IJavaElement.COMPILATION_UNIT) {
+ compilationUnit = (ICompilationUnit)parentElement;
+ }
+
+ element = parentElement;
+ } else {
+ break;
+ }
+ } while (compilationUnit == null);
+
+ if (compilationUnit != null) {
+ compilationUnit.createImport(SdkConstants.CLASS_CONTEXT,
+ null /* sibling */, new NullProgressMonitor());
+ compilationUnit.createImport(SdkConstants.CLASS_INTENT,
+ null /* sibling */, new NullProgressMonitor());
+ }
+ } catch (JavaModelException e) {
+ // looks like the class already existed (this happens when the user check to create
+ // inherited abstract methods).
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ThemeAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ThemeAttributeDescriptor.java
new file mode 100644
index 000000000..881d75361
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/ThemeAttributeDescriptor.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.descriptors;
+
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ITextAttributeCreator;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode;
+import com.android.resources.ResourceType;
+
+/**
+ * Describes a Theme/Style XML attribute displayed by a {@link UiResourceAttributeNode}
+ * <p/>
+ * Used by the override for .../theme in {@link AndroidManifestDescriptors}.
+ */
+public final class ThemeAttributeDescriptor extends TextAttributeDescriptor {
+
+ /**
+ * Used by {@link DescriptorsUtils} to create instances of this descriptor.
+ */
+ public static final ITextAttributeCreator CREATOR = new ITextAttributeCreator() {
+ @Override
+ public TextAttributeDescriptor create(String xmlLocalName,
+ String nsUri, IAttributeInfo attrInfo) {
+ return new ThemeAttributeDescriptor(xmlLocalName, nsUri, attrInfo);
+ }
+ };
+
+ public ThemeAttributeDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo) {
+ super(xmlLocalName, nsUri, attrInfo);
+ }
+
+ /**
+ * @return A new {@link UiResourceAttributeNode} linked to this theme descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiResourceAttributeNode(ResourceType.STYLE, this, uiParent);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java
new file mode 100644
index 000000000..4c829d9ec
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java
@@ -0,0 +1,736 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.model;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextAttributeNode;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.xml.AndroidManifest;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.Flags;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.ui.IJavaElementSearchConstants;
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction;
+import org.eclipse.jdt.ui.dialogs.ITypeInfoFilterExtension;
+import org.eclipse.jdt.ui.dialogs.ITypeInfoRequestor;
+import org.eclipse.jdt.ui.dialogs.ITypeSelectionComponent;
+import org.eclipse.jdt.ui.dialogs.TypeSelectionExtension;
+import org.eclipse.jdt.ui.wizards.NewClassWizardPage;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.SelectionDialog;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.events.HyperlinkAdapter;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents an XML attribute for a class, that can be modified using a simple text field or
+ * a dialog to choose an existing class. Also, there's a link to create a new class.
+ * <p/>
+ * See {@link UiTextAttributeNode} for more information.
+ */
+public class UiClassAttributeNode extends UiTextAttributeNode {
+
+ private String mReferenceClass;
+ private IPostTypeCreationAction mPostCreationAction;
+ private boolean mMandatory;
+ private final boolean mDefaultToProjectOnly;
+
+ private class HierarchyTypeSelection extends TypeSelectionExtension {
+
+ private IJavaProject mJavaProject;
+ private IType mReferenceType;
+ private Button mProjectOnly;
+ private boolean mUseProjectOnly;
+
+ public HierarchyTypeSelection(IProject project, String referenceClass)
+ throws JavaModelException {
+ mJavaProject = JavaCore.create(project);
+ mReferenceType = mJavaProject.findType(referenceClass);
+ }
+
+ @Override
+ public ITypeInfoFilterExtension getFilterExtension() {
+ return new ITypeInfoFilterExtension() {
+ @Override
+ public boolean select(ITypeInfoRequestor typeInfoRequestor) {
+
+ boolean projectOnly = mUseProjectOnly;
+
+ String packageName = typeInfoRequestor.getPackageName();
+ String typeName = typeInfoRequestor.getTypeName();
+ String enclosingType = typeInfoRequestor.getEnclosingName();
+
+ // build the full class name.
+ StringBuilder sb = new StringBuilder(packageName);
+ sb.append('.');
+ if (enclosingType.length() > 0) {
+ sb.append(enclosingType);
+ sb.append('.');
+ }
+ sb.append(typeName);
+
+ String className = sb.toString();
+
+ try {
+ IType type = mJavaProject.findType(className);
+
+ if (type == null) {
+ return false;
+ }
+
+ // don't display abstract classes
+ if ((type.getFlags() & Flags.AccAbstract) != 0) {
+ return false;
+ }
+
+ // if project-only is selected, make sure the package fragment is
+ // an actual source (thus "from this project").
+ if (projectOnly) {
+ IPackageFragment frag = type.getPackageFragment();
+ if (frag == null || frag.getKind() != IPackageFragmentRoot.K_SOURCE) {
+ return false;
+ }
+ }
+
+ // get the type hierarchy and reference type is one of the super classes.
+ ITypeHierarchy hierarchy = type.newSupertypeHierarchy(
+ new NullProgressMonitor());
+
+ IType[] supertypes = hierarchy.getAllSupertypes(type);
+ int n = supertypes.length;
+ for (int i = 0; i < n; i++) {
+ IType st = supertypes[i];
+ if (mReferenceType.equals(st)) {
+ return true;
+ }
+ }
+ } catch (JavaModelException e) {
+ }
+
+ return false;
+ }
+ };
+ }
+
+ @Override
+ public Control createContentArea(Composite parent) {
+
+ mProjectOnly = new Button(parent, SWT.CHECK);
+ mProjectOnly.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mProjectOnly.setText(String.format("Display classes from sources of project '%s' only",
+ mJavaProject.getProject().getName()));
+
+ mUseProjectOnly = mDefaultToProjectOnly;
+ mProjectOnly.setSelection(mUseProjectOnly);
+
+ mProjectOnly.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ mUseProjectOnly = mProjectOnly.getSelection();
+ getTypeSelectionComponent().triggerSearch();
+ }
+ });
+
+ return super.createContentArea(parent);
+ }
+ }
+
+ /**
+ * Classes which implement this interface provide a method processing newly created classes.
+ */
+ public static interface IPostTypeCreationAction {
+ /**
+ * Sent to process a newly created class.
+ * @param newType the IType representing the newly created class.
+ */
+ public void processNewType(IType newType);
+ }
+
+ /**
+ * Creates a {@link UiClassAttributeNode} object that will display ui to select or create
+ * classes.
+ * @param referenceClass The allowed supertype of the classes that are to be selected
+ * or created. Can be null.
+ * @param postCreationAction a {@link IPostTypeCreationAction} object handling post creation
+ * modification of the class.
+ * @param mandatory indicates if the class value is mandatory
+ * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node.
+ * @param defaultToProjectOnly When true display classes of this project only by default.
+ * When false any class path will be considered. The user can always toggle this.
+ */
+ public UiClassAttributeNode(String referenceClass, IPostTypeCreationAction postCreationAction,
+ boolean mandatory, AttributeDescriptor attributeDescriptor, UiElementNode uiParent,
+ boolean defaultToProjectOnly) {
+ super(attributeDescriptor, uiParent);
+
+ mReferenceClass = referenceClass;
+ mPostCreationAction = postCreationAction;
+ mMandatory = mandatory;
+ mDefaultToProjectOnly = defaultToProjectOnly;
+ }
+
+ /* (non-java doc)
+ * Creates a label widget and an associated text field.
+ * <p/>
+ * As most other parts of the android manifest editor, this assumes the
+ * parent uses a table layout with 2 columns.
+ */
+ @Override
+ public void createUiControl(final Composite parent, IManagedForm managedForm) {
+ setManagedForm(managedForm);
+ FormToolkit toolkit = managedForm.getToolkit();
+ TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
+
+ StringBuilder label = new StringBuilder();
+ label.append("<form><p><a href='unused'>");
+ label.append(desc.getUiName());
+ label.append("</a></p></form>");
+ FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */,
+ label.toString(), true /* setupLayoutData */);
+ formText.addHyperlinkListener(new HyperlinkAdapter() {
+ @Override
+ public void linkActivated(HyperlinkEvent e) {
+ super.linkActivated(e);
+ handleLabelClick();
+ }
+ });
+ formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
+ SectionHelper.addControlTooltip(formText, desc.getTooltip());
+
+ Composite composite = toolkit.createComposite(parent);
+ composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));
+ GridLayout gl = new GridLayout(2, false);
+ gl.marginHeight = gl.marginWidth = 0;
+ composite.setLayout(gl);
+ // Fixes missing text borders under GTK... also requires adding a 1-pixel margin
+ // for the text field below
+ toolkit.paintBordersFor(composite);
+
+ final Text text = toolkit.createText(composite, getCurrentValue());
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK
+ text.setLayoutData(gd);
+ Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH);
+
+ setTextWidget(text);
+
+ browseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ handleBrowseClick();
+ }
+ });
+ }
+
+ /* (non-java doc)
+ *
+ * Add a modify listener that will check the validity of the class
+ */
+ @Override
+ protected void onAddValidators(final Text text) {
+ ModifyListener listener = new ModifyListener() {
+ @Override
+ public void modifyText(ModifyEvent e) {
+ try {
+ String textValue = text.getText().trim();
+ if (textValue.length() == 0) {
+ if (mMandatory) {
+ setErrorMessage("Value is mandatory", text);
+ } else {
+ setErrorMessage(null, text);
+ }
+ return;
+ }
+ // first we need the current java package.
+ String javaPackage = getManifestPackage();
+
+ // build the fully qualified name of the class
+ String className = AndroidManifest.combinePackageAndClassName(
+ javaPackage, textValue);
+
+ // only test the vilibility for activities.
+ boolean testVisibility = SdkConstants.CLASS_ACTIVITY.equals(
+ mReferenceClass);
+
+ // test the class
+ setErrorMessage(BaseProjectHelper.testClassForManifest(
+ BaseProjectHelper.getJavaProject(getProject()), className,
+ mReferenceClass, testVisibility), text);
+ } catch (CoreException ce) {
+ setErrorMessage(ce.getMessage(), text);
+ }
+ }
+ };
+
+ text.addModifyListener(listener);
+
+ // Make sure the validator removes its message(s) when the widget is disposed
+ text.addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(DisposeEvent e) {
+ // we don't want to use setErrorMessage, because we don't want to reset
+ // the error flag in the UiAttributeNode
+ getManagedForm().getMessageManager().removeMessage(text, text);
+ }
+ });
+
+ // Finally call the validator once to make sure the initial value is processed
+ listener.modifyText(null);
+ }
+
+ private void handleBrowseClick() {
+ Text text = getTextWidget();
+
+ // we need to get the project of the manifest.
+ IProject project = getProject();
+ if (project != null) {
+
+ // Create a search scope including only the source folder of the current
+ // project.
+ IPackageFragmentRoot[] packageFragmentRoots = getPackageFragmentRoots(project,
+ true /*include_containers*/);
+ IJavaSearchScope scope = SearchEngine.createJavaSearchScope(
+ packageFragmentRoots,
+ false);
+
+ try {
+ SelectionDialog dlg = JavaUI.createTypeDialog(text.getShell(),
+ PlatformUI.getWorkbench().getProgressService(),
+ scope,
+ IJavaElementSearchConstants.CONSIDER_CLASSES, // style
+ false, // no multiple selection
+ "**", //$NON-NLS-1$ //filter
+ new HierarchyTypeSelection(project, mReferenceClass));
+ dlg.setMessage(String.format("Select class name for element %1$s:",
+ getUiParent().getBreadcrumbTrailDescription(false /* include_root */)));
+ if (dlg instanceof ITypeSelectionComponent) {
+ ((ITypeSelectionComponent)dlg).triggerSearch();
+ }
+
+ if (dlg.open() == Window.OK) {
+ Object[] results = dlg.getResult();
+ if (results.length == 1) {
+ handleNewType((IType)results[0]);
+ }
+ }
+ } catch (JavaModelException e1) {
+ AdtPlugin.log(e1, "UiClassAttributeNode HandleBrowser failed");
+ }
+ }
+ }
+
+ private void handleLabelClick() {
+ // get the current value
+ String className = getTextWidget().getText().trim();
+
+ // get the package name from the manifest.
+ String packageName = getManifestPackage();
+
+ if (className.length() == 0) {
+ createNewClass(packageName, null /* className */);
+ } else {
+ // build back the fully qualified class name.
+ String fullClassName = className;
+ if (className.startsWith(".")) { //$NON-NLS-1$
+ fullClassName = packageName + className;
+ } else {
+ String[] segments = className.split(AdtConstants.RE_DOT);
+ if (segments.length == 1) {
+ fullClassName = packageName + "." + className; //$NON-NLS-1$
+ }
+ }
+
+ // in case the type is enclosed, we need to replace the $ with .
+ fullClassName = fullClassName.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$
+
+ // now we try to find the file that contains this class and we open it in the editor.
+ IProject project = getProject();
+ IJavaProject javaProject = JavaCore.create(project);
+
+ try {
+ IType result = javaProject.findType(fullClassName);
+ if (result != null) {
+ JavaUI.openInEditor(result);
+ } else {
+ // split the last segment from the fullClassname
+ int index = fullClassName.lastIndexOf('.');
+ if (index != -1) {
+ createNewClass(fullClassName.substring(0, index),
+ fullClassName.substring(index+1));
+ } else {
+ createNewClass(packageName, className);
+ }
+ }
+ } catch (JavaModelException e) {
+ AdtPlugin.log(e, "UiClassAttributeNode HandleLabel failed");
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, "UiClassAttributeNode HandleLabel failed");
+ }
+ }
+ }
+
+ private IProject getProject() {
+ UiElementNode uiNode = getUiParent();
+ AndroidXmlEditor editor = uiNode.getEditor();
+ IEditorInput input = editor.getEditorInput();
+ if (input instanceof IFileEditorInput) {
+ // from the file editor we can get the IFile object, and from it, the IProject.
+ IFile file = ((IFileEditorInput)input).getFile();
+ return file.getProject();
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Returns the current value of the /manifest/package attribute.
+ * @return the package or an empty string if not found
+ */
+ private String getManifestPackage() {
+ // get the root uiNode to get the 'package' attribute value.
+ UiElementNode rootNode = getUiParent().getUiRoot();
+
+ Element xmlElement = (Element) rootNode.getXmlNode();
+
+ if (xmlElement != null) {
+ return xmlElement.getAttribute(AndroidManifestDescriptors.PACKAGE_ATTR);
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+
+ /**
+ * Computes and return the {@link IPackageFragmentRoot}s corresponding to the source folders of
+ * the specified project.
+ * @param project the project
+ * @param include_containers True to include containers
+ * @return an array of IPackageFragmentRoot.
+ */
+ private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project,
+ boolean include_containers) {
+ ArrayList<IPackageFragmentRoot> result = new ArrayList<IPackageFragmentRoot>();
+ try {
+ IJavaProject javaProject = JavaCore.create(project);
+ IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
+ for (int i = 0; i < roots.length; i++) {
+ IClasspathEntry entry = roots[i].getRawClasspathEntry();
+ if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE ||
+ (include_containers &&
+ entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER)) {
+ result.add(roots[i]);
+ }
+ }
+ } catch (JavaModelException e) {
+ }
+
+ return result.toArray(new IPackageFragmentRoot[result.size()]);
+ }
+
+ private void handleNewType(IType type) {
+ Text text = getTextWidget();
+
+ // get the fully qualified name with $ to properly detect the enclosing types.
+ String name = type.getFullyQualifiedName('$');
+
+ String packageValue = getManifestPackage();
+
+ // check if the class doesn't start with the package.
+ if (packageValue.length() > 0 && name.startsWith(packageValue)) {
+ // if it does, we remove the package and the first dot.
+ name = name.substring(packageValue.length() + 1);
+
+ // look for how many segments we have left.
+ // if one, just write it that way.
+ // if more than one, write it with a leading dot.
+ String[] packages = name.split(AdtConstants.RE_DOT);
+ if (packages.length == 1) {
+ text.setText(name);
+ } else {
+ text.setText("." + name); //$NON-NLS-1$
+ }
+ } else {
+ text.setText(name);
+ }
+ }
+
+ private void createNewClass(String packageName, String className) {
+ // create the wizard page for the class creation, and configure it
+ NewClassWizardPage page = new NewClassWizardPage();
+
+ // set the parent class
+ page.setSuperClass(mReferenceClass, true /* canBeModified */);
+
+ // get the source folders as java elements.
+ IPackageFragmentRoot[] roots = getPackageFragmentRoots(getProject(),
+ true /*include_containers*/);
+
+ IPackageFragmentRoot currentRoot = null;
+ IPackageFragment currentFragment = null;
+ int packageMatchCount = -1;
+
+ for (IPackageFragmentRoot root : roots) {
+ // Get the java element for the package.
+ // This method is said to always return a IPackageFragment even if the
+ // underlying folder doesn't exist...
+ IPackageFragment fragment = root.getPackageFragment(packageName);
+ if (fragment != null && fragment.exists()) {
+ // we have a perfect match! we use it.
+ currentRoot = root;
+ currentFragment = fragment;
+ packageMatchCount = -1;
+ break;
+ } else {
+ // we don't have a match. we look for the fragment with the best match
+ // (ie the closest parent package we can find)
+ try {
+ IJavaElement[] children;
+ children = root.getChildren();
+ for (IJavaElement child : children) {
+ if (child instanceof IPackageFragment) {
+ fragment = (IPackageFragment)child;
+ if (packageName.startsWith(fragment.getElementName())) {
+ // its a match. get the number of segments
+ String[] segments = fragment.getElementName().split("\\."); //$NON-NLS-1$
+ if (segments.length > packageMatchCount) {
+ packageMatchCount = segments.length;
+ currentFragment = fragment;
+ currentRoot = root;
+ }
+ }
+ }
+ }
+ } catch (JavaModelException e) {
+ // Couldn't get the children: we just ignore this package root.
+ }
+ }
+ }
+
+ ArrayList<IPackageFragment> createdFragments = null;
+
+ if (currentRoot != null) {
+ // if we have a perfect match, we set it and we're done.
+ if (packageMatchCount == -1) {
+ page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/);
+ page.setPackageFragment(currentFragment, true /* canBeModified */);
+ } else {
+ // we have a partial match.
+ // create the package. We have to start with the first segment so that we
+ // know what to delete in case of a cancel.
+ try {
+ createdFragments = new ArrayList<IPackageFragment>();
+
+ int totalCount = packageName.split("\\.").length; //$NON-NLS-1$
+ int count = 0;
+ int index = -1;
+ // skip the matching packages
+ while (count < packageMatchCount) {
+ index = packageName.indexOf('.', index+1);
+ count++;
+ }
+
+ // create the rest of the segments, except for the last one as indexOf will
+ // return -1;
+ while (count < totalCount - 1) {
+ index = packageName.indexOf('.', index+1);
+ count++;
+ createdFragments.add(currentRoot.createPackageFragment(
+ packageName.substring(0, index),
+ true /* force*/, new NullProgressMonitor()));
+ }
+
+ // create the last package
+ createdFragments.add(currentRoot.createPackageFragment(
+ packageName, true /* force*/, new NullProgressMonitor()));
+
+ // set the root and fragment in the Wizard page
+ page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/);
+ page.setPackageFragment(createdFragments.get(createdFragments.size()-1),
+ true /* canBeModified */);
+ } catch (JavaModelException e) {
+ // if we can't create the packages, there's a problem. we revert to the default
+ // package
+ for (IPackageFragmentRoot root : roots) {
+ // Get the java element for the package.
+ // This method is said to always return a IPackageFragment even if the
+ // underlying folder doesn't exist...
+ IPackageFragment fragment = root.getPackageFragment(packageName);
+ if (fragment != null && fragment.exists()) {
+ page.setPackageFragmentRoot(root, true /* canBeModified*/);
+ page.setPackageFragment(fragment, true /* canBeModified */);
+ break;
+ }
+ }
+ }
+ }
+ } else if (roots.length > 0) {
+ // if we haven't found a valid fragment, we set the root to the first source folder.
+ page.setPackageFragmentRoot(roots[0], true /* canBeModified*/);
+ }
+
+ // if we have a starting class name we use it
+ if (className != null) {
+ page.setTypeName(className, true /* canBeModified*/);
+ }
+
+ // create the action that will open it the wizard.
+ OpenNewClassWizardAction action = new OpenNewClassWizardAction();
+ action.setConfiguredWizardPage(page);
+ action.run();
+ IJavaElement element = action.getCreatedElement();
+
+ if (element != null) {
+ if (element.getElementType() == IJavaElement.TYPE) {
+
+ IType type = (IType)element;
+
+ if (mPostCreationAction != null) {
+ mPostCreationAction.processNewType(type);
+ }
+
+ handleNewType(type);
+ }
+ } else {
+ // lets delete the packages we created just for this.
+ // we need to start with the leaf and go up
+ if (createdFragments != null) {
+ try {
+ for (int i = createdFragments.size() - 1 ; i >= 0 ; i--) {
+ createdFragments.get(i).delete(true /* force*/, new NullProgressMonitor());
+ }
+ } catch (JavaModelException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the error messages. If message is <code>null</code>, the message is removed.
+ * @param message the message to set, or <code>null</code> to remove the current message
+ * @param textWidget the {@link Text} widget associated to the message.
+ */
+ private final void setErrorMessage(String message, Text textWidget) {
+ if (message != null) {
+ setHasError(true);
+ getManagedForm().getMessageManager().addMessage(textWidget, message, null /* data */,
+ IMessageProvider.ERROR, textWidget);
+ } else {
+ setHasError(false);
+ getManagedForm().getMessageManager().removeMessage(textWidget, textWidget);
+ }
+ }
+
+ @Override
+ public String[] getPossibleValues(String prefix) {
+ // Compute a list of existing classes for content assist completion
+ IProject project = getProject();
+ if (project == null || mReferenceClass == null) {
+ return null;
+ }
+
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ IType type = javaProject.findType(mReferenceClass);
+ // Use sets because query sometimes repeats the same class
+ Set<String> libraryTypes = new HashSet<String>(80);
+ Set<String> localTypes = new HashSet<String>(30);
+ if (type != null) {
+ ITypeHierarchy hierarchy = type.newTypeHierarchy(new NullProgressMonitor());
+ IType[] allSubtypes = hierarchy.getAllSubtypes(type);
+ for (IType subType : allSubtypes) {
+ int flags = subType.getFlags();
+ if (Flags.isPublic(flags) && !Flags.isAbstract(flags)) {
+ String fqcn = subType.getFullyQualifiedName();
+ if (subType.getResource() != null) {
+ localTypes.add(fqcn);
+ } else {
+ libraryTypes.add(fqcn);
+ }
+ }
+ }
+ }
+
+ List<String> local = new ArrayList<String>(localTypes);
+ List<String> library = new ArrayList<String>(libraryTypes);
+ Collections.sort(local);
+ Collections.sort(library);
+ List<String> combined = new ArrayList<String>(local.size() + library.size());
+ combined.addAll(local);
+ combined.addAll(library);
+ return combined.toArray(new String[combined.size()]);
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+}
+
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestElementNode.java
new file mode 100644
index 000000000..0151d4d46
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestElementNode.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.model;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.ManifestElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+
+import org.w3c.dom.Element;
+
+/**
+ * Represents an XML node that can be modified by the user interface in the XML editor.
+ * <p/>
+ * Each tree viewer used in the application page's parts needs to keep a model representing
+ * each underlying node in the tree. This interface represents the base type for such a node.
+ * <p/>
+ * Each node acts as an intermediary model between the actual XML model (the real data support)
+ * and the tree viewers or the corresponding page parts.
+ * <p/>
+ * Element nodes don't contain data per se. Their data is contained in their attributes
+ * as well as their children's attributes, see {@link UiAttributeNode}.
+ * <p/>
+ * The structure of a given {@link UiElementNode} is declared by a corresponding
+ * {@link ElementDescriptor}.
+ */
+public final class UiManifestElementNode extends UiElementNode {
+
+ /**
+ * Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}.
+ *
+ * @param elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null.
+ */
+ public UiManifestElementNode(ManifestElementDescriptor elementDescriptor) {
+ super(elementDescriptor);
+ }
+
+ /**
+ * Computes a short string describing the UI node suitable for tree views.
+ * Uses the element's attribute "android:name" if present, or the "android:label" one
+ * followed by the element's name if not repeated.
+ *
+ * @return A short string describing the UI node suitable for tree views.
+ */
+ @Override
+ public String getShortDescription() {
+ AndroidTargetData target = getAndroidTarget();
+ AndroidManifestDescriptors manifestDescriptors = null;
+ if (target != null) {
+ manifestDescriptors = target.getManifestDescriptors();
+ }
+
+ String name = getDescriptor().getUiName();
+
+ if (manifestDescriptors != null &&
+ getXmlNode() != null &&
+ getXmlNode() instanceof Element &&
+ getXmlNode().hasAttributes()) {
+
+ // Application and Manifest nodes have a special treatment: they are unique nodes
+ // so we don't bother trying to differentiate their strings and we fall back to
+ // just using the UI name below.
+ ElementDescriptor desc = getDescriptor();
+ if (desc != manifestDescriptors.getManifestElement() &&
+ desc != manifestDescriptors.getApplicationElement()) {
+ Element elem = (Element) getXmlNode();
+ String attr = _Element_getAttributeNS(elem,
+ SdkConstants.NS_RESOURCES,
+ AndroidManifestDescriptors.ANDROID_NAME_ATTR);
+ if (attr == null || attr.length() == 0) {
+ attr = _Element_getAttributeNS(elem,
+ SdkConstants.NS_RESOURCES,
+ AndroidManifestDescriptors.ANDROID_LABEL_ATTR);
+ }
+ if (attr != null && attr.length() > 0) {
+ // If the ui name is repeated in the attribute value, don't use it.
+ // Typical case is to avoid ".pkg.MyActivity (Activity)".
+ if (attr.contains(name)) {
+ return attr;
+ } else {
+ return String.format("%1$s (%2$s)", attr, name);
+ }
+ }
+ }
+ }
+
+ return String.format("%1$s", name);
+ }
+
+ /**
+ * Retrieves an attribute value by local name and namespace URI.
+ * <br>Per [<a href='http://www.w3.org/TR/1999/REC-xml-names-19990114/'>XML Namespaces</a>]
+ * , applications must use the value <code>null</code> as the
+ * <code>namespaceURI</code> parameter for methods if they wish to have
+ * no namespace.
+ * <p/>
+ * Note: This is a wrapper around {@link Element#getAttributeNS(String, String)}.
+ * In some versions of webtools, the getAttributeNS implementation crashes with an NPE.
+ * This wrapper will return null instead.
+ *
+ * @see Element#getAttributeNS(String, String)
+ * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=318108">https://bugs.eclipse.org/bugs/show_bug.cgi?id=318108</a>
+ * @return The result from {@link Element#getAttributeNS(String, String)} or or an empty string.
+ */
+ private String _Element_getAttributeNS(Element element,
+ String namespaceURI,
+ String localName) {
+ try {
+ return element.getAttributeNS(namespaceURI, localName);
+ } catch (Exception ignore) {
+ return "";
+ }
+ }
+}
+
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java
new file mode 100755
index 000000000..60d9125f6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestPkgAttrNode.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.model;
+
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextAttributeNode;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+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.wizards.actions.NewProjectAction;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizard;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+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.Text;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.ElementListSelectionDialog;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.events.HyperlinkAdapter;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.part.FileEditorInput;
+
+import java.util.TreeSet;
+
+/**
+ * Represents an XML attribute to select an existing manifest package, that can be modified using
+ * a simple text field or a dialog to choose an existing package.
+ * <p/>
+ * See {@link UiTextAttributeNode} for more information.
+ */
+public class UiManifestPkgAttrNode extends UiTextAttributeNode {
+
+ /**
+ * Creates a {@link UiManifestPkgAttrNode} object that will display ui to select or create
+ * a manifest package.
+ * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node.
+ */
+ public UiManifestPkgAttrNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) {
+ super(attributeDescriptor, uiParent);
+ }
+
+ /* (non-java doc)
+ * Creates a label widget and an associated text field.
+ * <p/>
+ * As most other parts of the android manifest editor, this assumes the
+ * parent uses a table layout with 2 columns.
+ */
+ @Override
+ public void createUiControl(final Composite parent, final IManagedForm managedForm) {
+ setManagedForm(managedForm);
+ FormToolkit toolkit = managedForm.getToolkit();
+ TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
+
+ StringBuilder label = new StringBuilder();
+ label.append("<form><p><a href='unused'>"); //$NON-NLS-1$
+ label.append(desc.getUiName());
+ label.append("</a></p></form>"); //$NON-NLS-1$
+ FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */,
+ label.toString(), true /* setupLayoutData */);
+ formText.addHyperlinkListener(new HyperlinkAdapter() {
+ @Override
+ public void linkActivated(HyperlinkEvent e) {
+ super.linkActivated(e);
+ doLabelClick();
+ }
+ });
+ formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
+ SectionHelper.addControlTooltip(formText, desc.getTooltip());
+
+ Composite composite = toolkit.createComposite(parent);
+ composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));
+ GridLayout gl = new GridLayout(2, false);
+ gl.marginHeight = gl.marginWidth = 0;
+ composite.setLayout(gl);
+ // Fixes missing text borders under GTK... also requires adding a 1-pixel margin
+ // for the text field below
+ toolkit.paintBordersFor(composite);
+
+ final Text text = toolkit.createText(composite, getCurrentValue());
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK
+ text.setLayoutData(gd);
+
+ setTextWidget(text);
+
+ Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH);
+
+ browseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ doBrowseClick();
+ }
+ });
+
+ }
+
+ /* (non-java doc)
+ * Adds a validator to the text field that calls managedForm.getMessageManager().
+ */
+ @Override
+ protected void onAddValidators(final Text text) {
+ ModifyListener listener = new ModifyListener() {
+ @Override
+ public void modifyText(ModifyEvent e) {
+ String package_name = text.getText();
+ if (package_name.indexOf('.') < 1) {
+ getManagedForm().getMessageManager().addMessage(text,
+ "Package name should contain at least two identifiers.",
+ null /* data */, IMessageProvider.ERROR, text);
+ } else {
+ getManagedForm().getMessageManager().removeMessage(text, text);
+ }
+ }
+ };
+
+ text.addModifyListener(listener);
+
+ // Make sure the validator removes its message(s) when the widget is disposed
+ text.addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(DisposeEvent e) {
+ getManagedForm().getMessageManager().removeMessage(text, text);
+ }
+ });
+
+ // Finally call the validator once to make sure the initial value is processed
+ listener.modifyText(null);
+ }
+
+ /**
+ * Handles response to the Browse button by creating a Package dialog.
+ * */
+ private void doBrowseClick() {
+
+ // Display the list of AndroidManifest packages in a selection dialog
+ ElementListSelectionDialog dialog = new ElementListSelectionDialog(
+ getTextWidget().getShell(),
+ new ILabelProvider() {
+ @Override
+ public Image getImage(Object element) {
+ return null;
+ }
+
+ @Override
+ public String getText(Object element) {
+ return element.toString();
+ }
+
+ @Override
+ public void addListener(ILabelProviderListener listener) {
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ @Override
+ public void removeListener(ILabelProviderListener listener) {
+ }
+ });
+
+ dialog.setTitle("Android Manifest Package Selection");
+ dialog.setMessage("Select the Android Manifest package to target.");
+
+ dialog.setElements(getPossibleValues(null));
+
+ // open the dialog and use the object selected if OK was clicked, or null otherwise
+ if (dialog.open() == Window.OK) {
+ String result = (String) dialog.getFirstResult();
+ if (result != null && result.length() > 0) {
+ getTextWidget().setText(result);
+ }
+ }
+ }
+
+ /**
+ * Handles response to the Label hyper link being activated.
+ */
+ private void doLabelClick() {
+ // get the current package name
+ String package_name = getTextWidget().getText().trim();
+
+ if (package_name.length() == 0) {
+ createNewProject();
+ } else {
+ displayExistingManifest(package_name);
+ }
+ }
+
+ /**
+ * When the label is clicked and there's already a package name, this method
+ * attempts to find the project matching the android package name and it attempts
+ * to open the manifest editor.
+ *
+ * @param package_name The android package name to find. Must not be null.
+ */
+ private void displayExistingManifest(String package_name) {
+
+ // Look for the first project that uses this package name
+ for (IJavaProject project : BaseProjectHelper.getAndroidProjects(null /*filter*/)) {
+ // check that there is indeed a manifest file.
+ IFile manifestFile = ProjectHelper.getManifest(project.getProject());
+ if (manifestFile == null) {
+ // no file? skip this project.
+ continue;
+ }
+
+ ManifestData manifestData = AndroidManifestHelper.parseForData(manifestFile);
+ if (manifestData == null) {
+ // skip this project.
+ continue;
+ }
+
+ if (package_name.equals(manifestData.getPackage())) {
+ // Found the project.
+
+ IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (win != null) {
+ IWorkbenchPage page = win.getActivePage();
+ if (page != null) {
+ try {
+ page.openEditor(
+ new FileEditorInput(manifestFile),
+ ManifestEditor.ID,
+ true, /* activate */
+ IWorkbenchPage.MATCH_INPUT);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e,
+ "Opening editor failed for %s", //$NON-NLS-1$
+ manifestFile.getFullPath());
+ }
+ }
+ }
+
+ // We found the project; even if we failed there's no need to keep looking.
+ return;
+ }
+ }
+ }
+
+ /**
+ * Displays the New Project Wizard to create a new project.
+ * If one is successfully created, use the Android Package name.
+ */
+ private void createNewProject() {
+
+ NewProjectAction npwAction = new NewProjectAction();
+ npwAction.run(null /*action*/);
+ if (npwAction.getDialogResult() == Dialog.OK) {
+ NewProjectWizard npw = (NewProjectWizard) npwAction.getWizard();
+ String name = npw.getPackageName();
+ if (name != null && name.length() > 0) {
+ getTextWidget().setText(name);
+ }
+ }
+ }
+
+ /**
+ * Returns all the possible android package names that could be used.
+ * The prefix is not used.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] getPossibleValues(String prefix) {
+ TreeSet<String> packages = new TreeSet<String>();
+
+ for (IJavaProject project : BaseProjectHelper.getAndroidProjects(null /*filter*/)) {
+ // check that there is indeed a manifest file.
+ ManifestData manifestData = AndroidManifestHelper.parseForData(project.getProject());
+ if (manifestData == null) {
+ // skip this project.
+ continue;
+ }
+
+ packages.add(manifestData.getPackage());
+ }
+
+ return packages.toArray(new String[packages.size()]);
+ }
+}
+
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiPackageAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiPackageAttributeNode.java
new file mode 100644
index 000000000..e6a2007b3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiPackageAttributeNode.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.model;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextAttributeNode;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jdt.ui.actions.OpenNewPackageWizardAction;
+import org.eclipse.jdt.ui.actions.ShowInPackageViewAction;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+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.Text;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.IWorkbenchPartSite;
+import org.eclipse.ui.dialogs.SelectionDialog;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.events.HyperlinkAdapter;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+
+import java.util.ArrayList;
+
+/**
+ * Represents an XML attribute for a package, that can be modified using a simple text field or
+ * a dialog to choose an existing package. Also, there's a link to create a new package.
+ * <p/>
+ * See {@link UiTextAttributeNode} for more information.
+ */
+public class UiPackageAttributeNode extends UiTextAttributeNode {
+
+ /**
+ * Creates a {@link UiPackageAttributeNode} object that will display ui to select or create
+ * a package.
+ * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node.
+ */
+ public UiPackageAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) {
+ super(attributeDescriptor, uiParent);
+ }
+
+ /* (non-java doc)
+ * Creates a label widget and an associated text field.
+ * <p/>
+ * As most other parts of the android manifest editor, this assumes the
+ * parent uses a table layout with 2 columns.
+ */
+ @Override
+ public void createUiControl(final Composite parent, final IManagedForm managedForm) {
+ setManagedForm(managedForm);
+ FormToolkit toolkit = managedForm.getToolkit();
+ TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
+
+ StringBuilder label = new StringBuilder();
+ label.append("<form><p><a href='unused'>"); //$NON-NLS-1$
+ label.append(desc.getUiName());
+ label.append("</a></p></form>"); //$NON-NLS-1$
+ FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */,
+ label.toString(), true /* setupLayoutData */);
+ formText.addHyperlinkListener(new HyperlinkAdapter() {
+ @Override
+ public void linkActivated(HyperlinkEvent e) {
+ super.linkActivated(e);
+ doLabelClick();
+ }
+ });
+ formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
+ SectionHelper.addControlTooltip(formText, desc.getTooltip());
+
+ Composite composite = toolkit.createComposite(parent);
+ composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));
+ GridLayout gl = new GridLayout(2, false);
+ gl.marginHeight = gl.marginWidth = 0;
+ composite.setLayout(gl);
+ // Fixes missing text borders under GTK... also requires adding a 1-pixel margin
+ // for the text field below
+ toolkit.paintBordersFor(composite);
+
+ final Text text = toolkit.createText(composite, getCurrentValue());
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK
+ text.setLayoutData(gd);
+
+ setTextWidget(text);
+
+ Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH);
+
+ browseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ doBrowseClick();
+ }
+ });
+
+ }
+
+ /* (non-java doc)
+ * Adds a validator to the text field that calls managedForm.getMessageManager().
+ */
+ @Override
+ protected void onAddValidators(final Text text) {
+ ModifyListener listener = new ModifyListener() {
+ @Override
+ public void modifyText(ModifyEvent e) {
+ String package_name = text.getText();
+ if (package_name.indexOf('.') < 1) {
+ getManagedForm().getMessageManager().addMessage(text,
+ "Package name should contain at least two identifiers.",
+ null /* data */, IMessageProvider.ERROR, text);
+ } else {
+ getManagedForm().getMessageManager().removeMessage(text, text);
+ }
+ }
+ };
+
+ text.addModifyListener(listener);
+
+ // Make sure the validator removes its message(s) when the widget is disposed
+ text.addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(DisposeEvent e) {
+ getManagedForm().getMessageManager().removeMessage(text, text);
+ }
+ });
+
+ // Finally call the validator once to make sure the initial value is processed
+ listener.modifyText(null);
+ }
+
+ /**
+ * Handles response to the Browse button by creating a Package dialog.
+ * */
+ private void doBrowseClick() {
+ Text text = getTextWidget();
+
+ // we need to get the project of the manifest.
+ IProject project = getProject();
+ if (project != null) {
+
+ try {
+ SelectionDialog dlg = JavaUI.createPackageDialog(text.getShell(),
+ JavaCore.create(project), 0);
+ dlg.setTitle("Select Android Package");
+ dlg.setMessage("Select the package for the Android project.");
+ SelectionDialog.setDefaultImage(AdtPlugin.getAndroidLogo());
+
+ if (dlg.open() == Window.OK) {
+ Object[] results = dlg.getResult();
+ if (results.length == 1) {
+ setPackageTextField((IPackageFragment)results[0]);
+ }
+ }
+ } catch (JavaModelException e1) {
+ }
+ }
+ }
+
+ /**
+ * Handles response to the Label hyper link being activated.
+ */
+ private void doLabelClick() {
+ // get the current package name
+ String package_name = getTextWidget().getText().trim();
+
+ if (package_name.length() == 0) {
+ createNewPackage();
+ } else {
+ // Try to select the package in the Package Explorer for the current
+ // project and the current editor's site.
+
+ IProject project = getProject();
+ if (project == null) {
+ AdtPlugin.log(IStatus.ERROR, "Failed to get project for UiPackageAttribute"); //$NON-NLS-1$
+ return;
+ }
+
+ IWorkbenchPartSite site = getUiParent().getEditor().getSite();
+ if (site == null) {
+ AdtPlugin.log(IStatus.ERROR, "Failed to get editor site for UiPackageAttribute"); //$NON-NLS-1$
+ return;
+ }
+
+ for (IPackageFragmentRoot root : getPackageFragmentRoots(project)) {
+ IPackageFragment fragment = root.getPackageFragment(package_name);
+ if (fragment != null && fragment.exists()) {
+ ShowInPackageViewAction action = new ShowInPackageViewAction(site);
+ action.run(fragment);
+ // This action's run() doesn't provide the status (although internally it could)
+ // so we just assume it worked.
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Utility method that returns the project for the current file being edited.
+ *
+ * @return The IProject for the current file being edited or null.
+ */
+ private IProject getProject() {
+ UiElementNode uiNode = getUiParent();
+ AndroidXmlEditor editor = uiNode.getEditor();
+ IEditorInput input = editor.getEditorInput();
+ if (input instanceof IFileEditorInput) {
+ // from the file editor we can get the IFile object, and from it, the IProject.
+ IFile file = ((IFileEditorInput)input).getFile();
+ return file.getProject();
+ }
+
+ return null;
+ }
+
+ /**
+ * Utility method that computes and returns the list of {@link IPackageFragmentRoot}
+ * corresponding to the source folder of the specified project.
+ *
+ * @param project the project
+ * @return an array of IPackageFragmentRoot. Can be empty but not null.
+ */
+ private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project) {
+ ArrayList<IPackageFragmentRoot> result = new ArrayList<IPackageFragmentRoot>();
+ try {
+ IJavaProject javaProject = JavaCore.create(project);
+ IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
+ for (int i = 0; i < roots.length; i++) {
+ IClasspathEntry entry = roots[i].getRawClasspathEntry();
+ if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+ result.add(roots[i]);
+ }
+ }
+ } catch (JavaModelException e) {
+ }
+
+ return result.toArray(new IPackageFragmentRoot[result.size()]);
+ }
+
+ /**
+ * Utility method that sets the package's text field to the package fragment's name.
+ * */
+ private void setPackageTextField(IPackageFragment type) {
+ Text text = getTextWidget();
+
+ String name = type.getElementName();
+
+ text.setText(name);
+ }
+
+
+ /**
+ * Displays and handles a "Create Package Wizard".
+ *
+ * This is invoked by doLabelClick() when clicking on the hyperlink label with an
+ * empty package text field.
+ */
+ private void createNewPackage() {
+ OpenNewPackageWizardAction action = new OpenNewPackageWizardAction();
+
+ IProject project = getProject();
+ action.setSelection(new StructuredSelection(project));
+ action.run();
+
+ IJavaElement element = action.getCreatedElement();
+ if (element != null &&
+ element.exists() &&
+ element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
+ setPackageTextField((IPackageFragment) element);
+ }
+ }
+
+ @Override
+ public String[] getPossibleValues(String prefix) {
+ // TODO: compute a list of existing packages for content assist completion
+ return null;
+ }
+}
+
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationAttributesPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationAttributesPart.java
new file mode 100644
index 000000000..7d3f6a89f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationAttributesPart.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.ui.UiElementPart;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+
+/**
+ * Application's attributes section part for Application page.
+ * <p/>
+ * This part is displayed at the top of the application page and displays all the possible
+ * attributes of an application node in the AndroidManifest (icon, class name, label, etc.)
+ */
+final class ApplicationAttributesPart extends UiElementPart {
+
+ /** Listen to changes to the UI node for <application> and updates the UI */
+ private AppNodeUpdateListener mAppNodeUpdateListener;
+ /** ManagedForm needed to create the UI controls */
+ private IManagedForm mManagedForm;
+
+ public ApplicationAttributesPart(Composite body, FormToolkit toolkit, ManifestEditor editor,
+ UiElementNode applicationUiNode) {
+ super(body, toolkit, editor, applicationUiNode,
+ "Application Attributes", // section title
+ "Defines the attributes specific to the application.", // section description
+ Section.TWISTIE | Section.EXPANDED);
+ }
+
+ /**
+ * Changes and refreshes the Application UI node handle by the this part.
+ */
+ @Override
+ public void setUiElementNode(UiElementNode uiElementNode) {
+ super.setUiElementNode(uiElementNode);
+
+ createUiAttributes(mManagedForm);
+ }
+
+ /* (non-java doc)
+ * Create the controls to edit the attributes for the given ElementDescriptor.
+ * <p/>
+ * This MUST not be called by the constructor. Instead it must be called from
+ * <code>initialize</code> (i.e. right after the form part is added to the managed form.)
+ * <p/>
+ * Derived classes can override this if necessary.
+ *
+ * @param managedForm The owner managed form
+ */
+ @Override
+ protected void createFormControls(final IManagedForm managedForm) {
+ mManagedForm = managedForm;
+ setTable(createTableLayout(managedForm.getToolkit(), 4 /* numColumns */));
+
+ mAppNodeUpdateListener = new AppNodeUpdateListener();
+ getUiElementNode().addUpdateListener(mAppNodeUpdateListener);
+
+ createUiAttributes(mManagedForm);
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ if (getUiElementNode() != null && mAppNodeUpdateListener != null) {
+ getUiElementNode().removeUpdateListener(mAppNodeUpdateListener);
+ mAppNodeUpdateListener = null;
+ }
+ }
+
+ @Override
+ protected void createUiAttributes(IManagedForm managedForm) {
+ Composite table = getTable();
+ if (table == null || managedForm == null) {
+ return;
+ }
+
+ // Remove any old UI controls
+ for (Control c : table.getChildren()) {
+ c.dispose();
+ }
+
+ UiElementNode uiElementNode = getUiElementNode();
+ AttributeDescriptor[] attr_desc_list = uiElementNode.getAttributeDescriptors();
+
+ // Display the attributes in 2 columns:
+ // attr 0 | attr 4
+ // attr 1 | attr 5
+ // attr 2 | attr 6
+ // attr 3 | attr 7
+ // that is we have to fill the grid in order 0, 4, 1, 5, 2, 6, 3, 7
+ // thus index = i/2 + (i is odd * n/2)
+ int n = attr_desc_list.length;
+ int n2 = (int) Math.ceil(n / 2.0);
+ for (int i = 0; i < n; i++) {
+ AttributeDescriptor attr_desc = attr_desc_list[i / 2 + (i & 1) * n2];
+ if (attr_desc instanceof XmlnsAttributeDescriptor) {
+ // Do not show hidden attributes
+ continue;
+ }
+
+ UiAttributeNode ui_attr = uiElementNode.findUiAttribute(attr_desc);
+ if (ui_attr != null) {
+ ui_attr.createUiControl(table, managedForm);
+ } else {
+ // The XML has an extra attribute which wasn't declared in
+ // AndroidManifestDescriptors. This is not a problem, we just ignore it.
+ AdtPlugin.log(IStatus.WARNING,
+ "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$
+ attr_desc.getXmlLocalName(),
+ uiElementNode.getDescriptor().getXmlName());
+ }
+ }
+
+ if (n == 0) {
+ createLabel(table, managedForm.getToolkit(),
+ "No attributes to display, waiting for SDK to finish loading...",
+ null /* tooltip */ );
+ }
+
+ // Initialize the enabled/disabled state
+ if (mAppNodeUpdateListener != null) {
+ mAppNodeUpdateListener.uiElementNodeUpdated(uiElementNode, null /* state, not used */);
+ }
+
+ // Tell the section that the layout has changed.
+ layoutChanged();
+ }
+
+ /**
+ * This listener synchronizes the UI with the actual presence of the application XML node.
+ */
+ private class AppNodeUpdateListener implements IUiUpdateListener {
+ @Override
+ public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) {
+ // The UiElementNode for the application XML node always exists, even
+ // if there is no corresponding XML node in the XML file.
+ //
+ // We enable the UI here if the XML node is not null.
+ Composite table = getTable();
+ boolean exists = (ui_node.getXmlNode() != null);
+ if (table != null && table.getEnabled() != exists) {
+ table.setEnabled(exists);
+ for (Control c : table.getChildren()) {
+ c.setEnabled(exists);
+ }
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationPage.java
new file mode 100644
index 000000000..06a3d3f3e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationPage.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiTreeBlock;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormPage;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+
+/**
+ * Page for "Application" settings, part of the AndroidManifest form editor.
+ * <p/>
+ * Useful reference:
+ * <a href="http://www.eclipse.org/articles/Article-Forms/article.html">
+ * http://www.eclipse.org/articles/Article-Forms/article.html</a>
+ */
+public final class ApplicationPage extends FormPage implements IPageImageProvider {
+ /** Page id used for switching tabs programmatically */
+ public final static String PAGE_ID = "application_page"; //$NON-NLS-1$
+
+ /** Container editor */
+ ManifestEditor mEditor;
+ /** The Application Toogle part */
+ private ApplicationToggle mTooglePart;
+ /** The Application Attributes part */
+ private ApplicationAttributesPart mAttrPart;
+ /** The tree view block */
+ private UiTreeBlock mTreeBlock;
+
+ public ApplicationPage(ManifestEditor editor) {
+ super(editor, PAGE_ID, "Application"); // tab's label, keep it short
+ mEditor = editor;
+ }
+
+ @Override
+ public Image getPageImage() {
+ return IconFactory.getInstance().getIcon(getTitle(),
+ IconFactory.COLOR_BLUE,
+ IconFactory.SHAPE_RECT);
+ }
+
+ /**
+ * Creates the content in the form hosted in this page.
+ *
+ * @param managedForm the form hosted in this page.
+ */
+ @Override
+ protected void createFormContent(IManagedForm managedForm) {
+ super.createFormContent(managedForm);
+ ScrolledForm form = managedForm.getForm();
+ form.setText("Android Manifest Application");
+ form.setImage(AdtPlugin.getAndroidLogo());
+
+ UiElementNode appUiNode = getUiApplicationNode();
+
+ Composite body = form.getBody();
+ FormToolkit toolkit = managedForm.getToolkit();
+
+ // We usually prefer to have a ColumnLayout here. However
+ // MasterDetailsBlock.createContent() below will reset the body's layout to a grid layout.
+ mTooglePart = new ApplicationToggle(body, toolkit, mEditor, appUiNode);
+ mTooglePart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ managedForm.addPart(mTooglePart);
+ mAttrPart = new ApplicationAttributesPart(body, toolkit, mEditor, appUiNode);
+ mAttrPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ managedForm.addPart(mAttrPart);
+
+ mTreeBlock = new UiTreeBlock(mEditor, appUiNode,
+ false /* autoCreateRoot */,
+ null /* element filters */,
+ "Application Nodes",
+ "List of all elements in the application");
+ mTreeBlock.createContent(managedForm);
+ }
+
+ /**
+ * Retrieves the application UI node. Since this is a mandatory node, it *always*
+ * exists, even if there is no matching XML node.
+ */
+ private UiElementNode getUiApplicationNode() {
+ AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors();
+ if (manifestDescriptor != null) {
+ ElementDescriptor desc = manifestDescriptor.getApplicationElement();
+ return mEditor.getUiRootNode().findUiChildNode(desc.getXmlName());
+ } else {
+ // return the ui root node, as a dummy application root node.
+ return mEditor.getUiRootNode();
+ }
+ }
+
+ /**
+ * Changes and refreshes the Application UI node handled by the sub parts.
+ */
+ public void refreshUiApplicationNode() {
+ UiElementNode appUiNode = getUiApplicationNode();
+ if (mTooglePart != null) {
+ mTooglePart.setUiElementNode(appUiNode);
+ }
+ if (mAttrPart != null) {
+ mAttrPart.setUiElementNode(appUiNode);
+ }
+ if (mTreeBlock != null) {
+ mTreeBlock.changeRootAndDescriptors(appUiNode,
+ null /* element filters */,
+ true /* refresh */);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
new file mode 100644
index 000000000..159f08959
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.ui.UiElementPart;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.utils.SdkUtils;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+/**
+ * Appllication Toogle section part for application page.
+ */
+final class ApplicationToggle extends UiElementPart {
+
+ /** Checkbox indicating whether an application node is present */
+ private Button mCheckbox;
+ /** Listen to changes to the UI node for <application> and updates the checkbox */
+ private AppNodeUpdateListener mAppNodeUpdateListener;
+ /** Internal flag to know where we're programmatically modifying the checkbox and we want to
+ * avoid triggering the checkbox's callback. */
+ public boolean mInternalModification;
+ private FormText mTooltipFormText;
+
+ public ApplicationToggle(Composite body, FormToolkit toolkit, ManifestEditor editor,
+ UiElementNode applicationUiNode) {
+ super(body, toolkit, editor, applicationUiNode,
+ "Application Toggle",
+ null, /* description */
+ Section.TWISTIE | Section.EXPANDED);
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ if (getUiElementNode() != null && mAppNodeUpdateListener != null) {
+ getUiElementNode().removeUpdateListener(mAppNodeUpdateListener);
+ mAppNodeUpdateListener = null;
+ }
+ }
+
+ /**
+ * Changes and refreshes the Application UI node handle by the this part.
+ */
+ @Override
+ public void setUiElementNode(UiElementNode uiElementNode) {
+ super.setUiElementNode(uiElementNode);
+
+ updateTooltip();
+
+ // Set the state of the checkbox
+ mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(),
+ UiUpdateState.CHILDREN_CHANGED);
+ }
+
+ /**
+ * Create the controls to edit the attributes for the given ElementDescriptor.
+ * <p/>
+ * This MUST not be called by the constructor. Instead it must be called from
+ * <code>initialize</code> (i.e. right after the form part is added to the managed form.)
+ *
+ * @param managedForm The owner managed form
+ */
+ @Override
+ protected void createFormControls(IManagedForm managedForm) {
+ FormToolkit toolkit = managedForm.getToolkit();
+ Composite table = createTableLayout(toolkit, 1 /* numColumns */);
+
+ mTooltipFormText = createFormText(table, toolkit, true, "<form></form>",
+ false /* setupLayoutData */);
+ updateTooltip();
+
+ mCheckbox = toolkit.createButton(table,
+ "Define an <application> tag in the AndroidManifest.xml",
+ SWT.CHECK);
+ mCheckbox.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP));
+ mCheckbox.setSelection(false);
+ mCheckbox.addSelectionListener(new CheckboxSelectionListener());
+
+ mAppNodeUpdateListener = new AppNodeUpdateListener();
+ getUiElementNode().addUpdateListener(mAppNodeUpdateListener);
+
+ // Initialize the state of the checkbox
+ mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(),
+ UiUpdateState.CHILDREN_CHANGED);
+
+ // Tell the section that the layout has changed.
+ layoutChanged();
+ }
+
+ /**
+ * Updates the application tooltip in the form text.
+ * If there is no tooltip, the form text is hidden.
+ */
+ private void updateTooltip() {
+ boolean isVisible = false;
+
+ String tooltip = getUiElementNode().getDescriptor().getTooltip();
+ if (tooltip != null) {
+ tooltip = DescriptorsUtils.formatFormText(tooltip,
+ getUiElementNode().getDescriptor(),
+ Sdk.getCurrent().getDocumentationBaseUrl());
+
+ mTooltipFormText.setText(tooltip, true /* parseTags */, true /* expandURLs */);
+ mTooltipFormText.setImage(DescriptorsUtils.IMAGE_KEY, AdtPlugin.getAndroidLogo());
+ mTooltipFormText.addHyperlinkListener(getEditor().createHyperlinkListener());
+ isVisible = true;
+ }
+
+ mTooltipFormText.setVisible(isVisible);
+ }
+
+ /**
+ * This listener synchronizes the XML application node when the checkbox
+ * is changed by the user.
+ */
+ private class CheckboxSelectionListener extends SelectionAdapter {
+ private Node mUndoXmlNode;
+ private Node mUndoXmlParent;
+ private Node mUndoXmlNextNode;
+ private Node mUndoXmlNextElement;
+ private Document mUndoXmlDocument;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ if (!mInternalModification && getUiElementNode() != null) {
+ getUiElementNode().getEditor().wrapUndoEditXmlModel(
+ mCheckbox.getSelection()
+ ? "Create or restore Application node"
+ : "Remove Application node",
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mCheckbox.getSelection()) {
+ // The user wants an <application> node.
+ // Either restore a previous one
+ // or create a full new one.
+ boolean create = true;
+ if (mUndoXmlNode != null) {
+ create = !restoreApplicationNode();
+ }
+ if (create) {
+ getUiElementNode().createXmlNode();
+ }
+ } else {
+ // Users no longer wants the <application> node.
+ removeApplicationNode();
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Restore a previously "saved" application node.
+ *
+ * @return True if the node could be restored, false otherwise.
+ */
+ private boolean restoreApplicationNode() {
+ if (mUndoXmlDocument == null || mUndoXmlNode == null) {
+ return false;
+ }
+
+ // Validate node references...
+ mUndoXmlParent = validateNode(mUndoXmlDocument, mUndoXmlParent);
+ mUndoXmlNextNode = validateNode(mUndoXmlDocument, mUndoXmlNextNode);
+ mUndoXmlNextElement = validateNode(mUndoXmlDocument, mUndoXmlNextElement);
+
+ if (mUndoXmlParent == null){
+ // If the parent node doesn't exist, try to find a new manifest node.
+ // If it doesn't exist, create it.
+ mUndoXmlParent = getUiElementNode().getUiParent().prepareCommit();
+ mUndoXmlNextNode = null;
+ mUndoXmlNextElement = null;
+ }
+
+ boolean success = false;
+ if (mUndoXmlParent != null) {
+ // If the parent is still around, reuse the same node.
+
+ // Ideally we want to insert the node before what used to be its next sibling.
+ // If that's not possible, we try to insert it before its next sibling element.
+ // If that's not possible either, it will be inserted at the end of the parent's.
+ Node next = mUndoXmlNextNode;
+ if (next == null) {
+ next = mUndoXmlNextElement;
+ }
+ mUndoXmlParent.insertBefore(mUndoXmlNode, next);
+ if (next == null) {
+ Text sep = mUndoXmlDocument.createTextNode(SdkUtils.getLineSeparator());
+ mUndoXmlParent.insertBefore(sep, null); // insert separator before end tag
+ }
+ success = true;
+ }
+
+ // Remove internal references to avoid using them twice
+ mUndoXmlParent = null;
+ mUndoXmlNextNode = null;
+ mUndoXmlNextElement = null;
+ mUndoXmlNode = null;
+ mUndoXmlDocument = null;
+ return success;
+ }
+
+ /**
+ * Validates that the given xml_node is still either the root node or one of its
+ * direct descendants.
+ *
+ * @param root_node The root of the node hierarchy to examine.
+ * @param xml_node The XML node to find.
+ * @return Returns xml_node if it is, otherwise returns null.
+ */
+ private Node validateNode(Node root_node, Node xml_node) {
+ if (root_node == xml_node) {
+ return xml_node;
+ } else {
+ for (Node node = root_node.getFirstChild(); node != null;
+ node = node.getNextSibling()) {
+ if (root_node == xml_node || validateNode(node, xml_node) != null) {
+ return xml_node;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Removes the <application> node from the hierarchy.
+ * Before doing that, we try to remember where it was so that we can put it back
+ * in the same place.
+ */
+ private void removeApplicationNode() {
+ // Make sure the node actually exists...
+ Node xml_node = getUiElementNode().getXmlNode();
+ if (xml_node == null) {
+ return;
+ }
+
+ // Save its parent, next sibling and next element
+ mUndoXmlDocument = xml_node.getOwnerDocument();
+ mUndoXmlParent = xml_node.getParentNode();
+ mUndoXmlNextNode = xml_node.getNextSibling();
+ mUndoXmlNextElement = mUndoXmlNextNode;
+ while (mUndoXmlNextElement != null &&
+ mUndoXmlNextElement.getNodeType() != Node.ELEMENT_NODE) {
+ mUndoXmlNextElement = mUndoXmlNextElement.getNextSibling();
+ }
+
+ // Actually remove the node from the hierarchy and keep it here.
+ // The returned node looses its parents/siblings pointers.
+ mUndoXmlNode = getUiElementNode().deleteXmlNode();
+ }
+ }
+
+ /**
+ * This listener synchronizes the UI (i.e. the checkbox) with the
+ * actual presence of the application XML node.
+ */
+ private class AppNodeUpdateListener implements IUiUpdateListener {
+ @Override
+ public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) {
+ // The UiElementNode for the application XML node always exists, even
+ // if there is no corresponding XML node in the XML file.
+ //
+ // To update the checkbox to reflect the actual state, we just need
+ // to check if the XML node is null.
+ try {
+ mInternalModification = true;
+ boolean exists = ui_node.getXmlNode() != null;
+ if (mCheckbox.getSelection() != exists) {
+ mCheckbox.setSelection(exists);
+ }
+ } finally {
+ mInternalModification = false;
+ }
+
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/InstrumentationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/InstrumentationPage.java
new file mode 100644
index 000000000..a8bb34691
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/InstrumentationPage.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiTreeBlock;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormPage;
+import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+/**
+ * Page for instrumentation settings, part of the AndroidManifest form editor.
+ */
+public final class InstrumentationPage extends FormPage implements IPageImageProvider {
+ /** Page id used for switching tabs programmatically */
+ public final static String PAGE_ID = "instrumentation_page"; //$NON-NLS-1$
+
+ /** Container editor */
+ ManifestEditor mEditor;
+
+ private UiTreeBlock mTreeBlock;
+
+ public InstrumentationPage(ManifestEditor editor) {
+ super(editor, PAGE_ID, "Instrumentation"); // tab's label, keep it short
+ mEditor = editor;
+ }
+
+ @Override
+ public Image getPageImage() {
+ return IconFactory.getInstance().getIcon(getTitle(),
+ IconFactory.COLOR_GREEN,
+ IconFactory.SHAPE_RECT);
+ }
+
+ /**
+ * Creates the content in the form hosted in this page.
+ *
+ * @param managedForm the form hosted in this page.
+ */
+ @Override
+ protected void createFormContent(IManagedForm managedForm) {
+ super.createFormContent(managedForm);
+ ScrolledForm form = managedForm.getForm();
+ form.setText("Android Manifest Instrumentation");
+ form.setImage(AdtPlugin.getAndroidLogo());
+
+ UiElementNode manifest = mEditor.getUiRootNode();
+ AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors();
+
+ ElementDescriptor[] descriptorFilters = null;
+ if (manifestDescriptor != null) {
+ descriptorFilters = new ElementDescriptor[] {
+ manifestDescriptor.getInstrumentationElement(),
+ };
+ }
+
+ mTreeBlock = new UiTreeBlock(mEditor, manifest,
+ true /* autoCreateRoot */,
+ descriptorFilters,
+ "Instrumentation",
+ "List of instrumentations defined in the manifest");
+ mTreeBlock.createContent(managedForm);
+ }
+
+ /**
+ * Changes and refreshes the Application UI node handled by the sub parts.
+ */
+ public void refreshUiNode() {
+ if (mTreeBlock != null) {
+ UiElementNode manifest = mEditor.getUiRootNode();
+ AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors();
+
+ mTreeBlock.changeRootAndDescriptors(manifest,
+ new ElementDescriptor[] {
+ manifestDescriptor.getInstrumentationElement()
+ },
+ true /* refresh */);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java
new file mode 100644
index 000000000..b0eb75a2d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper.ManifestSectionPart;
+import com.android.ide.eclipse.adt.internal.project.ExportHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.wizard.WizardDialog;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.forms.events.HyperlinkAdapter;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.part.FileEditorInput;
+
+/**
+ * Export section part for overview page.
+ */
+final class OverviewExportPart extends ManifestSectionPart {
+
+ private final OverviewPage mOverviewPage;
+
+ public OverviewExportPart(OverviewPage overviewPage, final Composite body, FormToolkit toolkit,
+ ManifestEditor editor) {
+ super(body, toolkit, Section.TWISTIE | Section.EXPANDED, true /* description */);
+ mOverviewPage = overviewPage;
+ Section section = getSection();
+ section.setText("Exporting");
+
+ final IProject project = getProject();
+ boolean isLibrary = false;
+ if (project != null) {
+ ProjectState state = Sdk.getProjectState(project);
+ if (state != null) {
+ isLibrary = state.isLibrary();
+ }
+ }
+
+ if (isLibrary) {
+ section.setDescription("Library project cannot be exported.");
+ Composite table = createTableLayout(toolkit, 2 /* numColumns */);
+ createFormText(table, toolkit, true, "<form></form>", false /* setupLayoutData */);
+ } else {
+ section.setDescription("To export the application for distribution, you have the following options:");
+
+ Composite table = createTableLayout(toolkit, 2 /* numColumns */);
+
+ StringBuffer buf = new StringBuffer();
+ buf.append("<form><li><a href=\"wizard\">"); //$NON-NLS-1$
+ buf.append("Use the Export Wizard");
+ buf.append("</a>"); //$NON-NLS-1$
+ buf.append(" to export and sign an APK");
+ buf.append("</li>"); //$NON-NLS-1$
+ buf.append("<li><a href=\"manual\">"); //$NON-NLS-1$
+ buf.append("Export an unsigned APK");
+ buf.append("</a>"); //$NON-NLS-1$
+ buf.append(" and sign it manually");
+ buf.append("</li></form>"); //$NON-NLS-1$
+
+ FormText text = createFormText(table, toolkit, true, buf.toString(),
+ false /* setupLayoutData */);
+ text.addHyperlinkListener(new HyperlinkAdapter() {
+ @Override
+ public void linkActivated(HyperlinkEvent e) {
+ if (project != null) {
+ if ("manual".equals(e.data)) { //$NON-NLS-1$
+ // now we can export an unsigned apk for the project.
+ ExportHelper.exportUnsignedReleaseApk(project);
+ } else {
+ // call the export wizard
+ StructuredSelection selection = new StructuredSelection(project);
+
+ ExportWizard wizard = new ExportWizard();
+ wizard.init(PlatformUI.getWorkbench(), selection);
+ WizardDialog dialog = new WizardDialog(body.getShell(), wizard);
+ dialog.open();
+ }
+ }
+ }
+ });
+ }
+
+ layoutChanged();
+ }
+
+ /**
+ * Returns the project of the edited file.
+ */
+ private IProject getProject() {
+ IEditorInput input = mOverviewPage.mEditor.getEditorInput();
+ if (input instanceof FileEditorInput) {
+ FileEditorInput fileInput = (FileEditorInput)input;
+ IFile file = fileInput.getFile();
+ return file.getProject();
+ }
+
+ return null;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewInfoPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewInfoPart.java
new file mode 100644
index 000000000..98f2f9cc2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewInfoPart.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.ui.UiElementPart;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+
+/**
+ * Generic info section part for overview page: it displays all the attributes from
+ * the manifest element.
+ */
+final class OverviewInfoPart extends UiElementPart {
+
+ private IManagedForm mManagedForm;
+
+ public OverviewInfoPart(Composite body, FormToolkit toolkit, ManifestEditor editor) {
+ super(body, toolkit, editor,
+ getManifestUiNode(editor), // uiElementNode
+ "Manifest General Attributes", // section title
+ "Defines general information about the AndroidManifest.xml", // section description
+ Section.TWISTIE | Section.EXPANDED);
+ }
+
+ /**
+ * Retrieves the UiElementNode that this part will edit. The node must exist
+ * and can't be null, by design, because it's a mandatory node.
+ */
+ private static UiElementNode getManifestUiNode(ManifestEditor editor) {
+ AndroidManifestDescriptors manifestDescriptors = editor.getManifestDescriptors();
+ if (manifestDescriptors != null) {
+ ElementDescriptor desc = manifestDescriptors.getManifestElement();
+ if (editor.getUiRootNode().getDescriptor() == desc) {
+ return editor.getUiRootNode();
+ } else {
+ return editor.getUiRootNode().findUiChildNode(desc.getXmlName());
+ }
+ }
+
+ // No manifest descriptor: we have a dummy UiRootNode, so we return that.
+ // The editor will be reloaded once we have the proper descriptors anyway.
+ return editor.getUiRootNode();
+ }
+
+ /**
+ * Overridden in order to capture the current managed form.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected void createFormControls(final IManagedForm managedForm) {
+ mManagedForm = managedForm;
+ super.createFormControls(managedForm);
+ }
+
+ /**
+ * Removes any existing Attribute UI widgets and recreate them if the SDK has changed.
+ * <p/>
+ * This is called by {@link OverviewPage#refreshUiApplicationNode()} when the
+ * SDK has changed.
+ */
+ public void onSdkChanged() {
+ setUiElementNode(getManifestUiNode(getEditor()));
+ createUiAttributes(mManagedForm);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewLinksPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewLinksPart.java
new file mode 100644
index 000000000..f8213753a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewLinksPart.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper.ManifestSectionPart;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+
+/**
+ * Links section part for overview page.
+ */
+final class OverviewLinksPart extends ManifestSectionPart {
+
+ private final ManifestEditor mEditor;
+ private FormText mFormText;
+
+ public OverviewLinksPart(Composite body, FormToolkit toolkit, ManifestEditor editor) {
+ super(body, toolkit, Section.TWISTIE | Section.EXPANDED, true /* description */);
+ mEditor = editor;
+ Section section = getSection();
+ section.setText("Links");
+ section.setDescription("The content of the Android Manifest is made up of three sections. You can also edit the XML directly.");
+
+ Composite table = createTableLayout(toolkit, 2 /* numColumns */);
+
+ StringBuffer buf = new StringBuffer();
+ buf.append(String.format("<form><li style=\"image\" value=\"app_img\"><a href=\"page:%1$s\">", //$NON-NLS-1$
+ ApplicationPage.PAGE_ID));
+ buf.append("Application");
+ buf.append("</a>"); //$NON-NLS-1$
+ buf.append(": Activities, intent filters, providers, services and receivers.");
+ buf.append("</li>"); //$NON-NLS-1$
+
+ buf.append(String.format("<li style=\"image\" value=\"perm_img\"><a href=\"page:%1$s\">", //$NON-NLS-1$
+ PermissionPage.PAGE_ID));
+ buf.append("Permission");
+ buf.append("</a>"); //$NON-NLS-1$
+ buf.append(": Permissions defined and permissions used.");
+ buf.append("</li>"); //$NON-NLS-1$
+
+ buf.append(String.format("<li style=\"image\" value=\"inst_img\"><a href=\"page:%1$s\">", //$NON-NLS-1$
+ InstrumentationPage.PAGE_ID));
+ buf.append("Instrumentation");
+ buf.append("</a>"); //$NON-NLS-1$
+ buf.append(": Instrumentation defined.");
+ buf.append("</li>"); //$NON-NLS-1$
+
+ buf.append(String.format("<li style=\"image\" value=\"srce_img\"><a href=\"page:%1$s\">", //$NON-NLS-1$
+ ManifestEditor.TEXT_EDITOR_ID));
+ buf.append("XML Source");
+ buf.append("</a>"); //$NON-NLS-1$
+ buf.append(": Directly edit the AndroidManifest.xml file.");
+ buf.append("</li>"); //$NON-NLS-1$
+
+ buf.append("<li style=\"image\" value=\"android_img\">"); //$NON-NLS-1$
+ buf.append("<a href=\"http://code.google.com/android/devel/bblocks-manifest.html\">Documentation</a>: Documentation from the Android SDK for AndroidManifest.xml."); //$NON-NLS-1$
+ buf.append("</li>"); //$NON-NLS-1$
+ buf.append("</form>"); //$NON-NLS-1$
+
+ mFormText = createFormText(table, toolkit, true, buf.toString(),
+ false /* setupLayoutData */);
+
+ AndroidManifestDescriptors manifestDescriptor = editor.getManifestDescriptors();
+
+ Image androidLogo = AdtPlugin.getAndroidLogo();
+ mFormText.setImage("android_img", androidLogo); //$NON-NLS-1$
+ mFormText.setImage("srce_img", IconFactory.getInstance().getIcon(AndroidXmlEditor.ICON_XML_PAGE));
+
+ if (manifestDescriptor != null) {
+ mFormText.setImage("app_img", getIcon(manifestDescriptor.getApplicationElement())); //$NON-NLS-1$
+ mFormText.setImage("perm_img", getIcon(manifestDescriptor.getPermissionElement())); //$NON-NLS-1$
+ mFormText.setImage("inst_img", getIcon(manifestDescriptor.getInstrumentationElement())); //$NON-NLS-1$
+ } else {
+ mFormText.setImage("app_img", androidLogo); //$NON-NLS-1$
+ mFormText.setImage("perm_img", androidLogo); //$NON-NLS-1$
+ mFormText.setImage("inst_img", androidLogo); //$NON-NLS-1$
+ }
+ mFormText.addHyperlinkListener(editor.createHyperlinkListener());
+ }
+
+ /**
+ * Update the UI with information from the new descriptors.
+ * <p/>At this point, this only refreshes the icons.
+ * <p/>
+ * This is called by {@link OverviewPage#refreshUiApplicationNode()} when the
+ * SDK has changed.
+ */
+ public void onSdkChanged() {
+ AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors();
+ if (manifestDescriptor != null) {
+ mFormText.setImage("app_img", getIcon(manifestDescriptor.getApplicationElement())); //$NON-NLS-1$
+ mFormText.setImage("perm_img", getIcon(manifestDescriptor.getPermissionElement())); //$NON-NLS-1$
+ mFormText.setImage("inst_img", getIcon(manifestDescriptor.getInstrumentationElement())); //$NON-NLS-1$
+ }
+ }
+
+ private Image getIcon(ElementDescriptor desc) {
+ return desc.getCustomizedIcon();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewPage.java
new file mode 100644
index 000000000..7464f6a5f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewPage.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiTreeBlock;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormPage;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+
+/**
+ * Page for overview settings, part of the AndroidManifest form editor.
+ * <p/>
+ * Useful reference:
+ * <a href="http://www.eclipse.org/articles/Article-Forms/article.html">
+ * http://www.eclipse.org/articles/Article-Forms/article.html</a>
+ */
+public final class OverviewPage extends FormPage implements IPageImageProvider {
+
+ /** Page id used for switching tabs programmatically */
+ final static String PAGE_ID = "overview_page"; //$NON-NLS-1$
+
+ /** Container editor */
+ ManifestEditor mEditor;
+ /** Overview part (attributes for manifest) */
+ private OverviewInfoPart mOverviewPart;
+ /** Overview link part */
+ private OverviewLinksPart mOverviewLinkPart;
+
+ private UiTreeBlock mTreeBlock;
+
+ public OverviewPage(ManifestEditor editor) {
+ super(editor, PAGE_ID, "Manifest"); // tab's label, user visible, keep it short
+ mEditor = editor;
+ }
+
+ @Override
+ public Image getPageImage() {
+ return IconFactory.getInstance().getIcon("editor_page_design"); //$NON-NLS-1$
+ }
+
+ /**
+ * Creates the content in the form hosted in this page.
+ *
+ * @param managedForm the form hosted in this page.
+ */
+ @Override
+ protected void createFormContent(IManagedForm managedForm) {
+ super.createFormContent(managedForm);
+ ScrolledForm form = managedForm.getForm();
+ form.setText("Android Manifest");
+ form.setImage(AdtPlugin.getAndroidLogo());
+
+ Composite body = form.getBody();
+ FormToolkit toolkit = managedForm.getToolkit();
+
+ // Usually we would set a ColumnLayout on body here. However the presence of the
+ // UiTreeBlock forces a GridLayout with one column so we comply with it.
+
+ mOverviewPart = new OverviewInfoPart(body, toolkit, mEditor);
+ mOverviewPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ managedForm.addPart(mOverviewPart);
+
+ newManifestExtrasPart(managedForm);
+
+ OverviewExportPart exportPart = new OverviewExportPart(this, body, toolkit, mEditor);
+ exportPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ managedForm.addPart(exportPart);
+
+ mOverviewLinkPart = new OverviewLinksPart(body, toolkit, mEditor);
+ mOverviewLinkPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ managedForm.addPart(mOverviewLinkPart);
+ }
+
+ private void newManifestExtrasPart(IManagedForm managedForm) {
+ UiElementNode manifest = mEditor.getUiRootNode();
+ mTreeBlock = new UiTreeBlock(mEditor, manifest,
+ true /* autoCreateRoot */,
+ computeManifestExtraFilters(),
+ "Manifest Extras",
+ "Extra manifest elements");
+ mTreeBlock.createContent(managedForm);
+ }
+
+ /**
+ * Changes and refreshes the Application UI node handled by the sub parts.
+ */
+ public void refreshUiApplicationNode() {
+ if (mOverviewPart != null) {
+ mOverviewPart.onSdkChanged();
+ }
+
+ if (mOverviewLinkPart != null) {
+ mOverviewLinkPart.onSdkChanged();
+ }
+
+ if (mTreeBlock != null) {
+ UiElementNode manifest = mEditor.getUiRootNode();
+ mTreeBlock.changeRootAndDescriptors(manifest,
+ computeManifestExtraFilters(),
+ true /* refresh */);
+ }
+ }
+
+ private ElementDescriptor[] computeManifestExtraFilters() {
+ UiElementNode manifest = mEditor.getUiRootNode();
+ AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors();
+
+ if (manifestDescriptor == null) {
+ return null;
+ }
+
+ // get the elements we want to exclude
+ HashSet<ElementDescriptor> excludes = new HashSet<ElementDescriptor>();
+ excludes.add(manifestDescriptor.getApplicationElement());
+ excludes.add(manifestDescriptor.getInstrumentationElement());
+ excludes.add(manifestDescriptor.getPermissionElement());
+ excludes.add(manifestDescriptor.getPermissionGroupElement());
+ excludes.add(manifestDescriptor.getPermissionTreeElement());
+ excludes.add(manifestDescriptor.getUsesPermissionElement());
+
+ // walk through the known children of the manifest descriptor and keep what's not excluded
+ ArrayList<ElementDescriptor> descriptorFilters = new ArrayList<ElementDescriptor>();
+ for (ElementDescriptor child : manifest.getDescriptor().getChildren()) {
+ if (!excludes.contains(child)) {
+ descriptorFilters.add(child);
+ }
+ }
+
+ if (descriptorFilters.size() == 0) {
+ return null;
+ }
+ return descriptorFilters.toArray(new ElementDescriptor[descriptorFilters.size()]);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/PermissionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/PermissionPage.java
new file mode 100644
index 000000000..2f655777a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/PermissionPage.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.manifest.pages;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiTreeBlock;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormPage;
+import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+/**
+ * Page for permissions settings, part of the AndroidManifest form editor.
+ * <p/>
+ * Useful reference:
+ * <a href="http://www.eclipse.org/articles/Article-Forms/article.html">
+ * http://www.eclipse.org/articles/Article-Forms/article.html</a>
+ */
+public final class PermissionPage extends FormPage implements IPageImageProvider {
+ /** Page id used for switching tabs programmatically */
+ public final static String PAGE_ID = "permission_page"; //$NON-NLS-1$
+
+ /** Container editor */
+ ManifestEditor mEditor;
+
+ private UiTreeBlock mTreeBlock;
+
+ public PermissionPage(ManifestEditor editor) {
+ super(editor, PAGE_ID, "Permissions"); // tab label, keep it short
+ mEditor = editor;
+ }
+
+ @Override
+ public Image getPageImage() {
+ return IconFactory.getInstance().getIcon(getTitle(),
+ IconFactory.COLOR_RED,
+ IconFactory.SHAPE_RECT);
+ }
+
+ /**
+ * Creates the content in the form hosted in this page.
+ *
+ * @param managedForm the form hosted in this page.
+ */
+ @Override
+ protected void createFormContent(IManagedForm managedForm) {
+ super.createFormContent(managedForm);
+ ScrolledForm form = managedForm.getForm();
+ form.setText("Android Manifest Permissions");
+ form.setImage(AdtPlugin.getAndroidLogo());
+
+ UiElementNode manifest = mEditor.getUiRootNode();
+ AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors();
+
+ ElementDescriptor[] descriptorFilters = null;
+ if (manifestDescriptor != null) {
+ descriptorFilters = new ElementDescriptor[] {
+ manifestDescriptor.getPermissionElement(),
+ manifestDescriptor.getUsesPermissionElement(),
+ manifestDescriptor.getPermissionGroupElement(),
+ manifestDescriptor.getPermissionTreeElement()
+ };
+ }
+ mTreeBlock = new UiTreeBlock(mEditor, manifest,
+ true /* autoCreateRoot */,
+ descriptorFilters,
+ "Permissions",
+ "List of permissions defined and used by the manifest");
+ mTreeBlock.createContent(managedForm);
+ }
+
+ /**
+ * Changes and refreshes the Application UI node handled by the sub parts.
+ */
+ public void refreshUiNode() {
+ if (mTreeBlock != null) {
+ UiElementNode manifest = mEditor.getUiRootNode();
+ AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors();
+
+ mTreeBlock.changeRootAndDescriptors(manifest,
+ new ElementDescriptor[] {
+ manifestDescriptor.getPermissionElement(),
+ manifestDescriptor.getUsesPermissionElement(),
+ manifestDescriptor.getPermissionGroupElement(),
+ manifestDescriptor.getPermissionTreeElement()
+ },
+ true /* refresh */);
+ }
+ }
+}