17

I'm developing a mobile app using Ionic Framework (based on Cordova).

In Android I register my app to open *.txt files. I do it adding intent-filter in platforms/android/AndroidManifest.xml and it works. But platform folder is in .gitignore: I want to do it using config.xml.

I tried adding in config.xml:

     <platform name="android">
        <config-file target="AndroidManifest.xml" parent="/*/application/activity">
          <intent-filter><!-- ... --></intent-filter>
        </config-file>
        <!-- ... -->
     </platform>

And I tried also adding:

     <platform name="android">
        <config-file target="AndroidManifest.xml" parent="/manifest/application">
          <activity android:name="CordovaApp"> 
            <intent-filter><!-- ... --></intent-filter>
          </activity>
        </config-file>
        <!-- ... -->
     </platform>

Then I tried to update AndroidManifest launching

ionic prepare

Or also:

ionic remove platform android && ionic add platform android

But AndroidManifest.xml is always unchanged. What am I doing wrong?

I'm using Ionic 1.3.2 and Cordova 4.2.0.

Edit Here the entire config.xml:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <widget id="com.ionicframework.myapp551932" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
      <name>MyApp</name>
      <description>
            myApp
        </description>
      <author email="xxx@yyy.it" href="http://www.example.com/">
          A Team
        </author>
      <content src="index.html"/>
      <access origin="*"/>
      <preference name="webviewbounce" value="false"/>
      <preference name="UIWebViewBounce" value="false"/>
      <preference name="DisallowOverscroll" value="true"/>
      <preference name="BackupWebStorage" value="none"/>
      <preference name="SplashScreen" value="screen"/>
      <preference name="SplashScreenDelay" value="3000"/>
      <feature name="StatusBar">
        <param name="ios-package" value="CDVStatusBar" onload="true"/>
      </feature>
      <platform name="android">
        <config-file target="AndroidManifest.xml" parent="/manifest/application/activity">
          <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="file" />
            <data android:mimeType="*/*" />
            <data android:pathPattern=".*\\.txt" />
            <data android:host="*" />
          </intent-filter>
          <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="content" />
            <data android:pathPattern=".*\\.txt" />
            <data android:mimeType="*/*" />
          </intent-filter>
        </config-file>
        <icon src="resources/android/icon/drawable-ldpi-icon.png" density="ldpi"/>
        <icon src="resources/android/icon/drawable-mdpi-icon.png" density="mdpi"/>
        <icon src="resources/android/icon/drawable-hdpi-icon.png" density="hdpi"/>
        <icon src="resources/android/icon/drawable-xhdpi-icon.png" density="xhdpi"/>
        <icon src="resources/android/icon/drawable-xxhdpi-icon.png" density="xxhdpi"/>
        <icon src="resources/android/icon/drawable-xxxhdpi-icon.png" density="xxxhdpi"/>
        <splash src="resources/android/splash/drawable-land-ldpi-screen.png" density="land-ldpi"/>
        <splash src="resources/android/splash/drawable-land-mdpi-screen.png" density="land-mdpi"/>
        <splash src="resources/android/splash/drawable-land-hdpi-screen.png" density="land-hdpi"/>
        <splash src="resources/android/splash/drawable-land-xhdpi-screen.png" density="land-xhdpi"/>
        <splash src="resources/android/splash/drawable-land-xxhdpi-screen.png" density="land-xxhdpi"/>
        <splash src="resources/android/splash/drawable-land-xxxhdpi-screen.png" density="land-xxxhdpi"/>
        <splash src="resources/android/splash/drawable-port-ldpi-screen.png" density="port-ldpi"/>
        <splash src="resources/android/splash/drawable-port-mdpi-screen.png" density="port-mdpi"/>
        <splash src="resources/android/splash/drawable-port-hdpi-screen.png" density="port-hdpi"/>
        <splash src="resources/android/splash/drawable-port-xhdpi-screen.png" density="port-xhdpi"/>
        <splash src="resources/android/splash/drawable-port-xxhdpi-screen.png" density="port-xxhdpi"/>
        <splash src="resources/android/splash/drawable-port-xxxhdpi-screen.png" density="port-xxxhdpi"/>
      </platform>
      <icon src="resources/android/icon/drawable-xhdpi-icon.png"/>
    </widget>
Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78
Marco Carnazzo
  • 782
  • 2
  • 6
  • 25

4 Answers4

21

Resolved!

I can't do it using Ionic or Cordova: it's a PhoneGap feature (see this Stackoverflow answer)

I can do it in two other ways:

  1. Using a custom Cordova plugin
  2. Using a hook

I prefered the second way. I found an interesting hook for my purposes. Note: Rembember to install some packages:

npm install lodash elementtree plist --save-dev

Sadly this hook merges tags. So I wrote a little changed version of this hook: see here. You can put this hook in /hooks/after_platform_add.

Now I have my intent-filter configuration in config.xml:

  <platform name="android">
    <config-file target="AndroidManifest.xml" parent="application/activity">
      <intent-filter><!-- ... --></intent-filter>
    </config-file>
    <!-- ... -->
  </platform>

And I can update AndroidManifest.xml regenerating android platform:

ionic platform remove android && ionic platform add android
Community
  • 1
  • 1
Marco Carnazzo
  • 782
  • 2
  • 6
  • 25
  • Hook code is unavailable. Why not use GitHub's gists? – neoascetic Feb 15 '15 at 21:17
  • 1
    Ok. Moved to GitHub's gists :) – Marco Carnazzo Feb 16 '15 at 09:08
  • 10
    For those who aren't XML enthusiasts, if you get a cordova build error for `xml unbound prefix` then you need to add `xmlns:android="http://schemas.android.com/apk/res/android"` to the element at the top of your config – Deminetix Jul 07 '15 at 01:00
  • 1
    This solution is great, but means we need to destroy the android platform each time otherwise it continues to add multiple – Deminetix Jul 07 '15 at 01:01
  • 1
    I edited the answer with a little explanation: you can put the hook in /hooks/after_platform_add folder. So it edit AndroidManifest only one time. – Marco Carnazzo Jul 07 '15 at 20:56
  • 1
    nice, you should propose this to the cordova people, can't believe its not supported out of the box – Qiong Wu Oct 01 '15 at 23:35
9

Cordova 9 now directly supports using <config-file> and <edit-config> sections, like in plugin.xml files.

Without using any plugin or hook, you can directly do the following, for example, to add an intent filter to AndroidManifest.xml:

<?xml version='1.0' encoding='utf-8'?>
<widget id="yourdomain.app" version="1.7.8"
  xmlns="http://www.w3.org/ns/widgets"
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:cdv="http://cordova.apache.org/ns/1.0">
  <!-- ... -->
  <platform name="android">
    <!-- ... -->
    <config-file parent="application" target="AndroidManifest.xml">
      <activity android:label="webIntentFilter" android:name="yourdomain.app">
        <intent-filter android:autoVerify="true">
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.DEFAULT" />
          <category android:name="android.intent.category.BROWSABLE" />
          <data android:host="yourdomain.com" android:scheme="https" />
        </intent-filter>
      </activity>
    </config-file>
  </platform>
  <!-- ... -->
</widget>

Don't forget to add attribute xmlns:android="http://schemas.android.com/apk/res/android" into your <widget> tag to avoid unbound prefix error at build.

Raphaël Balet
  • 6,334
  • 6
  • 41
  • 78
Maxime Pacary
  • 22,336
  • 11
  • 85
  • 113
8

I had the same problem, but the idea of installing (and then remembering or documenting the dependency on) a bunch of npm dependencies and then using a big general-purpose hook was far too heavyweight for what I needed.

Hooks can be simple shell scripts which is often a much more straightforward way of modifying text files. In my case I only needed to add an intent-filter to the MainActivity activity which is a trivial job for sed; I just created the file hooks/after_prepare/020_add_moozvine_intents.sh with the content:

#!/usr/bin/env zsh

MANIFEST=${0:h}/../../platforms/android/AndroidManifest.xml
[[ -e $MANIFEST ]] || { print "Manifest not found at $MANIFEST." ; exit 1; }

grep -q HANDLE_MOOZVINE_NOTIFICATION $MANIFEST && { print "Manifest already modified. Nothing to do."; exit 0; }

AFTER_LINE='android:name="MainActivity"'
ADDITION='\
        <intent-filter>\
            <action android:name="HANDLE_MOOZVINE_NOTIFICATION" />\
            <category android:name="android.intent.category.DEFAULT" />\
        </intent-filter>
';

sed -i -e "/${AFTER_LINE}/a${ADDITION}" $MANIFEST

Job done. You can use a similar approach for any simple textual modifications to generated files.

Alexandre Bourlier
  • 3,972
  • 4
  • 44
  • 76
Rich
  • 997
  • 9
  • 12
  • 1
    I looked for this answer for hours. Thank you for sharing here. I was not able to think about it by lack of knowledge of shell scripts but... it works fine, thank you very much ! – Alexandre Bourlier Mar 30 '16 at 15:15
  • Excellent, thank you so much. I've just modified it to suit my needs and it works great :D – chonz0 May 03 '19 at 15:09
8

Here is the above solution by Rich written in JS for future Googlers as I had issues with the shell script.

module.exports = function (context) {
    const fs = require('fs');
    const _ = require('lodash');

    const scheme = 'flowkey';
    const insertIntent = `
    <intent-filter>
                <action android:name="android.intent.action.VIEW"></action>
                <category android:name="android.intent.category.DEFAULT"></category>
                <category android:name="android.intent.category.BROWSABLE"></category>
                <data android:scheme="${scheme}"></data>
    </intent-filter>
    `;
    const manifestPath = context.opts.projectRoot + '/platforms/android/AndroidManifest.xml';
    const androidManifest = fs.readFileSync(manifestPath).toString();
    if (!androidManifest.includes(`android:scheme="${scheme}"`)) {
        const manifestLines = androidManifest.split(/\r?\n/);
        const lineNo = _.findIndex(manifestLines, (line) => line.includes('@string/activity_name'));
        manifestLines.splice(lineNo + 1, 0, insertIntent);
        fs.writeFileSync(manifestPath, manifestLines.join('\n'));
    }
};

Use this as your after prepare hook.

Note: this is in ES6, you can find a ES5 version here: https://gist.github.com/smowden/f863331034bf300b960beef1ae25bf82

Christian Smorra
  • 1,756
  • 11
  • 13
  • This solution worked nicely for me when I put the script in scripts/cordova/hooks/android and added the appropriate after_prepare hook in config.xml – Chris Noldus Oct 12 '16 at 21:13