diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java | 578 |
1 files changed, 578 insertions, 0 deletions
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; + } + } + } + } + } + }); + } +} |