AndroidManifest file inspection

hello there, 

I developed an android studio plugin for code inspection, in certain point I need to add some xml tags to androidmanifest file programmatically. therefore I would like to ask how can I get androidmanifest file of the project? then, In order to add the xml tags can I use xmlTagBuilder?

Thanks in advance

21 comments
Comment actions Permalink

I tried the following code but it did not work

String pathToManifest = myProject.getBasePath() + "/app/src/main/AndroidManifest.xml";
VirtualFile manifestFile = LocalFileSystem.getInstance().findFileByPath(pathToManifest);
Module module = ModuleUtil.findModuleForFile(manifestFile,myProject);
XmlFile manifestContainingFile = (XmlFile) ManifestUtils.getAndroidManifestPsi(module).getContainingFile();
StringBuilder xmlTagBuilder = new StringBuilder();
xmlTagBuilder.append(" <activity android:name=\".TokenActivity");
xmlTagBuilder.append("\" />");
String xmlTag = xmlTagBuilder.toString();
XmlTag tagFromText = XmlElementFactory.getInstance(myProject).createTagFromText(xmlTag);
manifestContainingFile.getRootTag().addSubTag(tagFromText, true);

it give me an error that can not add the element.
0
Comment actions Permalink

Could you be more specific with error? Stacktrace or smth?

0
Comment actions Permalink

Yes, for sure. this is the stack trace of error:


com.intellij.util.IncorrectOperationException: Element cannot be added
at com.intellij.psi.impl.source.tree.CompositePsiElement.addInnerBefore(CompositePsiElement.java:322)
at com.intellij.psi.impl.source.tree.CompositePsiElement.addBefore(CompositePsiElement.java:156)
at com.intellij.psi.impl.source.xml.XmlTagImpl.addSubTag(XmlTagImpl.java:1028)

During the debugging I understand that the following line of code become Null and throws the exception:


TreeElement treeElement = addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE);

 

0
Comment actions Permalink

Seems the only way to debug and see, why addInternal fails. Have you checked that tagFromText is ok and doesn't contains error elements?

0
Comment actions Permalink

In order to check that every thing is fine I changed the code and write the code in the following way:

 AndroidFacet facet=AndroidFacet.getInstance(module);
final Manifest manifest = facet.getManifest();
if (manifest==null){
return;
}
final XmlTag manifestTag = manifest.getXmlTag();
if (manifestTag == null){
return;
}

final XmlTag applicationTag = manifestTag.findFirstSubTag("application");
if (applicationTag==null){
return;
}
// final XmlTag activityTag = applicationTag.findFirstSubTag("activity");
final PsiFile manifestFile = applicationTag.getContainingFile();
if (manifestFile == null){
return;
}
final VirtualFile vManifestFile = manifestFile.getVirtualFile();
if (vManifestFile == null || !ReadonlyStatusHandler.ensureFilesWritable(manifestFile.getProject(),vManifestFile)){
return;
}
XmlTag addTag = manifestTag.createChildTag("uses-sdk", "", null, false);
if (addTag != null) {
addTag = manifestTag.addSubTag(addTag, true);
addTag.setAttribute("minSdkVersion", SdkConstants.NS_RESOURCES,"16" );

I see all the values and it seems fine. the only thing that I think (not sure) that can be the reason is the fact that maybe anchor is the reason of the fail. Actually, I don't understand what does it mean?

If you have some suggestion to check for debugging, I will appreciate it. 

0
Comment actions Permalink

Also, check `addSubTag`/`createChildTag` usages in idea itself. May help.

0
Comment actions Permalink

Could you submit the AndroidManifest.xml file content to reproduce the problem?

0
Comment actions Permalink

Yes, Of course:

I enclosed the manifest file for your consideration. 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.amirsh.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

</manifest>

0
Comment actions Permalink

I would like to ask if there is a possibility for this error because I tried to insert in a wrong place? because the new activity tag should be inserted after the last </activity>.

so, I don't know that this fact can be the reason for the problem?

0
Comment actions Permalink

Thanks, the operation should be wrapped into a command, like

WriteCommandAction.runWriteCommandAction(myProject, () -> {file.getRootTag().addSubTag(subtag, true);});

I will try to add more descriptive error message here.

0
Comment actions Permalink

By modify the code to this version, it works for you?

0
Comment actions Permalink

I should wrap all part of the code into WriteCommandAction.runWriteCommandAction? 

0
Comment actions Permalink

No, just parts that modify PSI (in this case, addSubTag() call).

0
Comment actions Permalink

Thanks for your hint. Now, it is working for me as well. 

0
Comment actions Permalink

Final question in order to get the android facet I used the following code

Module[] modules = ModuleManager.getInstance(ProjectManager.getInstance().getOpenProjects()[0]).getSortedModules();
Module module = modules[modules.length-2];
AndroidFacet facet=AndroidFacet.getInstance(module);

Is there any better way to get the module object, because this method, maybe didn't work for different scenarios. 

Thanks in advance

0
Comment actions Permalink

Usually you have some context, which can help you to choose a module. Which one do you want? You are just taking arbitrary module from arbitrary project. What if you have several projects with several modules opened and some of them has no AndroidFacet?

What is the context of this exact code?

If you really need to get it in some very isolated place, you may iterate all opened projects and all their modules and try to get facet to for each module with first success winning. But such approach more likely indicates design flaw.

0
Comment actions Permalink

let me clarify the scenario. the scenario is as following, for example I have an android studio project with 4 modules that one of them are the app module that contains the Android Manifest file within it and the others can be different app modules like libraries, resources, etc. so, I need to get the app module, and then use it as an input to get the adnroidfacet. 

Thanks in advance 

0
Comment actions Permalink

Ok, and where do you need to do this? Some extension? Service? Action? Inspection?

0
Comment actions Permalink

I have an action class with different methods that the manifest modification happened in one of the methods that is placed in the action class.

0
Comment actions Permalink

`com.intellij.openapi.actionSystem.AnAction#actionPerformed` method invoked with `com.intellij.openapi.actionSystem.AnActionEvent` which may contains DataContext with different useful context information.

E.g. if your actin been invoked on psiFile, it's going to be in context. And by this psiFile you may find a module this file in.

To start your investigation check:

  • com.intellij.openapi.actionSystem.AnAction#getEventProject 
  • com.intellij.openapi.actionSystem.CommonDataKeys

 

0

Please sign in to leave a comment.