/* * 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. *
* 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