Skip to content

Commit

Permalink
fix(android)(8_0_X): Improved merge of "tiapp.xml" android manifest s…
Browse files Browse the repository at this point in the history
…ettings (#10900)

* [TIMOB-26778] Android: Improved merge of "tiapp.xml" file's android manifest settings into Titanium's default manifest settings

- [TIMOB-26778] Fixed bug where overriding an <activity/> in "tiapp.xml" caused most "configChanges" values to be lost.
  * Would cause activity window's UI to disappear if a system config change occurred dynamically.
- [TIMOB-26777] Fixed bug where connecting/disconnecting a physical keyboard to/from device caused UI to disappear.
- [TIMOB-27067] Fixed bug where UI sometimes disappears on Android 9.0 or higher when batter saver turns on/off.
- Added missing <activity/> "configChanges" values:
  * keyboard, layoutDirection, mcc, mnc, navigation, touchscreen, uiMode
- Removed launchMode "singleTask" from "TiMapActivity" and "TiVideoActivity". (Not applicable to child activities.)

* Android: Added new "allActivityConfigChanges" variable to be used by EJS "AndroidManifest.xml" template for [TIMOB-26778]

- Fixed previous [TIMOB-26778] commit to not inject configChanges values to all activities such as those belonging to modules/AARs.
  * Should only be added to Titanium activities that need it. They're now injected via "allActivityConfigChanges" EJS template variable.
- Removed last remnants of "Ti.Map" from build scripts.
- Removed "TiVideoActivity" and "TiCameraActivity" injection code from build script.
  * There is no downside to always having these activities in the "AndroidManifest.xml". So, it's been simplified.

* [TIMOB-27084] Android: Fixed issue where "tiapp.xml" was unable to override "AndroidManifest.xml" settings defined in AAR or "timodule.xml"

- Change manifest merge order. Now merges AAR and "timodule.xml" settings first. "tiapp.xml" settings are merged last.
  • Loading branch information
jquick-axway authored and keerthi1032 committed May 21, 2019
1 parent f71763c commit 2af5b74
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 131 deletions.
174 changes: 57 additions & 117 deletions android/cli/commands/_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -1857,7 +1857,6 @@ AndroidBuilder.prototype.initialize = function initialize(next) {
return appc.string.capitalize(word.toLowerCase());
}).join('');
/^[0-9]/.test(this.classname) && (this.classname = '_' + this.classname);
this.mainActivity = '.' + this.classname + 'Activity';

this.buildOnly = argv['build-only'];

Expand Down Expand Up @@ -3722,66 +3721,50 @@ AndroidBuilder.prototype.generateAndroidManifest = function generateAndroidManif
'Contacts.getAllGroups': contactsReadPermissions,
'Contacts.getGroupByID': contactsReadPermissions,

'Map.createView': geoPermissions,

'Media.Android.setSystemWallpaper': wallpaperPermissions,
'Media.showCamera': cameraPermissions,
'Media.vibrate': vibratePermissions,
},

tiMethodActivities = {
'Map.createView': {
activity: {
name: 'ti.modules.titanium.map.TiMapActivity',
configChanges: [ 'keyboardHidden', 'orientation' ],
launchMode: 'singleTask'
},
'uses-library': {
name: 'com.google.android.maps'
}
},
'Media.createVideoPlayer': {
activity: {
name: 'ti.modules.titanium.media.TiVideoActivity',
configChanges: [ 'keyboardHidden', 'orientation' ],
theme: '@style/Theme.AppCompat.Fullscreen',
launchMode: 'singleTask'
}
},
'Media.showCamera': {
activity: {
name: 'ti.modules.titanium.media.TiCameraActivity',
configChanges: [ 'keyboardHidden', 'orientation' ],
theme: '@style/Theme.AppCompat.Translucent.NoTitleBar.Fullscreen'
}
}
},

googleAPIs = [
'Map.createView'
// Example: 'Map.createView'
],

enableGoogleAPIWarning = this.target === 'emulator' && this.emulator && !this.emulator.googleApis,

fill = function (str) {
// first we replace all legacy variable placeholders with EJS style placeholders
str = str.replace(/(\$\{tiapp\.properties\[['"]([^'"]+)['"]\]\})/g, function (s, m1, m2) {
// if the property is the "id", we want to force our scrubbed "appid"
if (m2 === 'id') {
m2 = 'appid';
} else {
m2 = 'tiapp.' + m2;
}
return '<%- ' + m2 + ' %>';
});
// then process the string as an EJS template
return ejs.render(str, this);
}.bind(this),

finalAndroidManifest = (new AndroidManifest()).parse(fill(fs.readFileSync(path.join(this.templatesDir, 'AndroidManifest.xml')).toString())),
customAndroidManifest = this.customAndroidManifest,
tiappAndroidManifest = this.tiappAndroidManifest;

// Create a string of all known <activity/> attribute "android:configChanges" values for the target API Level.
// This variable will be referenced by name by an EJS "AndroidManifest.xml" template.
// Ex: <activity android:name="MyActivity" android:configChanges="<%- allActivityConfigChanges %>" />
this.allActivityConfigChanges
= 'fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation'
+ '|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode';
if (this.realTargetSDK >= 24) {
this.allActivityConfigChanges += '|density';
}

// Function used to EJS render Titanium's main "AndroidManfiest.xml" template and "timodule.xml" templates.
const ejsRenderManifest = function (str) {
// first we replace all legacy variable placeholders with EJS style placeholders
str = str.replace(/(\$\{tiapp\.properties\[['"]([^'"]+)['"]\]\})/g, function (s, m1, m2) {
// if the property is the "id", we want to force our scrubbed "appid"
if (m2 === 'id') {
m2 = 'appid';
} else {
m2 = 'tiapp.' + m2;
}
return '<%- ' + m2 + ' %>';
});
// then process the string as an EJS template
return ejs.render(str, this);
}.bind(this);

// Fetch main Titanium "AndroidManifest.xml" template's settings, resolving all template variables via EJS.
const finalAndroidManifest = (new AndroidManifest()).parse(ejsRenderManifest(
fs.readFileSync(path.join(this.templatesDir, 'AndroidManifest.xml')).toString()));

// if they are using a custom AndroidManifest and merging is disabled, then write the custom one as is
if (!this.config.get('android.mergeCustomAndroidManifest', true) && this.customAndroidManifest) {
(this.cli.createHook('build.android.writeAndroidManifest', this, function (file, xml, done) {
Expand Down Expand Up @@ -3823,18 +3806,6 @@ AndroidBuilder.prototype.generateAndroidManifest = function generateAndroidManif
});
}

const obj = tiMethodActivities[symbol];
if (obj) {
if (obj.activity) {
finalAndroidManifest.application.activity || (finalAndroidManifest.application.activity = {});
finalAndroidManifest.application.activity[obj.activity.name] = obj.activity;
}
if (obj['uses-library']) {
finalAndroidManifest.application['uses-library'] || (finalAndroidManifest.application['uses-library'] = {});
finalAndroidManifest.application['uses-library'][obj['uses-library'].name] = obj['uses-library'];
}
}

if (enableGoogleAPIWarning && googleAPIs.indexOf(symbol) !== -1) {
const fn = 'Titanium.' + symbol + '()';
if (this.emulator.googleApis === null) {
Expand All @@ -3859,14 +3830,6 @@ AndroidBuilder.prototype.generateAndroidManifest = function generateAndroidManif
this.logger.warn(__('Setting "%s" is not recommended for activity "%s"', 'android:launchMode'.red, activity.cyan));
}
}

// TIMOB-24917: make sure the main activity is themed
if (tiappAndroidManifest.application.activity && tiappAndroidManifest.application.activity[this.mainActivity]) {
const parameters = tiappAndroidManifest.application.activity[this.mainActivity];
if (!parameters.theme) {
parameters.theme = '@style/Theme.Titanium';
}
}
}

// gather activities
Expand All @@ -3882,7 +3845,6 @@ AndroidBuilder.prototype.generateAndroidManifest = function generateAndroidManif
a[key.replace(/^android:/, '')] = activity[key];
}
});
a.configChanges || (a.configChanges = [ 'keyboardHidden', 'orientation' ]);
finalAndroidManifest.application.activity || (finalAndroidManifest.application.activity = {});
finalAndroidManifest.application.activity[a.name] = a;
}
Expand Down Expand Up @@ -3932,19 +3894,35 @@ AndroidBuilder.prototype.generateAndroidManifest = function generateAndroidManif
// set the app icon
finalAndroidManifest.application.icon = '@drawable/' + this.tiapp.icon.replace(/((\.9)?\.(png|jpg))$/, '');

// merge the custom android manifest
finalAndroidManifest.merge(customAndroidManifest);
// Merge in "AndroidManifest.xml" files belonging to all AAR libraries.
this.androidLibraries.forEach(libraryInfo => {
const libraryManifestPath = path.join(libraryInfo.explodedPath, 'AndroidManifest.xml');
if (fs.existsSync(libraryManifestPath)) {
let libraryManifestContent = fs.readFileSync(libraryManifestPath).toString();

// merge the tiapp.xml android manifest
finalAndroidManifest.merge(tiappAndroidManifest);
// handle injected build variables such as ${applicationId}
// https://developer.android.com/studio/build/manifest-build-variables
libraryManifestContent = libraryManifestContent.replace(/\$\{applicationId\}/g, this.appid); // eslint-disable-line no-template-curly-in-string

const libraryManifest = new AndroidManifest();
libraryManifest.parse(libraryManifestContent);

// we don't want android libraries to override the <supports-screens> or <uses-sdk> tags
delete libraryManifest.__attr__;
delete libraryManifest['supports-screens'];
delete libraryManifest['uses-sdk'];
finalAndroidManifest.merge(libraryManifest);
}
});

// Merge in "AndroidManifest.xml" settings embedded within all "timodule.xml" files.
this.modules.forEach(function (module) {
const moduleXmlFile = path.join(module.modulePath, 'timodule.xml');
if (fs.existsSync(moduleXmlFile)) {
const moduleXml = new tiappxml(moduleXmlFile);
if (moduleXml.android && moduleXml.android.manifest) {
const am = new AndroidManifest();
am.parse(fill(moduleXml.android.manifest));
am.parse(ejsRenderManifest(moduleXml.android.manifest));
// we don't want modules to override the <supports-screens> or <uses-sdk> tags
delete am.__attr__;
delete am['supports-screens'];
Expand All @@ -3959,55 +3937,17 @@ AndroidBuilder.prototype.generateAndroidManifest = function generateAndroidManif
}
}, this);

this.androidLibraries.forEach(libraryInfo => {
const libraryManifestPath = path.join(libraryInfo.explodedPath, 'AndroidManifest.xml');
if (fs.existsSync(libraryManifestPath)) {
let libraryManifestContent = fs.readFileSync(libraryManifestPath).toString();

// handle injected build variables such as ${applicationId}
// https://developer.android.com/studio/build/manifest-build-variables
libraryManifestContent = libraryManifestContent.replace(/\$\{applicationId\}/g, this.appid); // eslint-disable-line no-template-curly-in-string

const libraryManifest = new AndroidManifest();
libraryManifest.parse(libraryManifestContent);

// we don't want android libraries to override the <supports-screens> or <uses-sdk> tags
delete libraryManifest.__attr__;
delete libraryManifest['supports-screens'];
delete libraryManifest['uses-sdk'];
finalAndroidManifest.merge(libraryManifest);
}
});
// Merge in the custom android manifest file.
finalAndroidManifest.merge(customAndroidManifest);

// if the target sdk is Android 3.2 or newer, then we need to add 'screenSize' to
// the default AndroidManifest.xml's 'configChanges' attribute for all <activity>
// elements, otherwise changes in orientation will cause the app to restart
if (this.realTargetSDK >= 13) {
Object.keys(finalAndroidManifest.application.activity).forEach(function (name) {
const activity = finalAndroidManifest.application.activity[name];
if (!activity.configChanges) {
activity.configChanges = [ 'screenSize' ];
} else if (activity.configChanges.indexOf('screenSize') === -1) {
activity.configChanges.push('screenSize');
}
});
}
// Merge in the "tiapp.xml" file's android manifest settings.
// Must be done last so app developer can override manifest settings such as <activity/>, <receiver/>, etc.
finalAndroidManifest.merge(tiappAndroidManifest);

if (this.realTargetSDK >= 24 && !finalAndroidManifest.application.hasOwnProperty('resizeableActivity')) {
finalAndroidManifest.application.resizeableActivity = true;
}

if (this.realTargetSDK >= 24) {
Object.keys(finalAndroidManifest.application.activity).forEach(function (name) {
const activity = finalAndroidManifest.application.activity[name];
if (!activity.configChanges) {
activity.configChanges = [ 'density' ];
} else if (activity.configChanges.indexOf('density') === -1) {
activity.configChanges.push('density');
}
});
}

// add permissions
if (!this.tiapp['override-permissions']) {
Array.isArray(finalAndroidManifest['uses-permission']) || (finalAndroidManifest['uses-permission'] = []);
Expand Down
43 changes: 36 additions & 7 deletions android/cli/lib/AndroidManifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ const appc = require('node-appc'),
},

tagAttrs = {
application: /^(allowTaskReparenting|allowBackup|backupAgent|backupInForeground|banner|debuggable|description|directBootAware|enabled|extractNativeLibs|fullBackupContent|fullBackupOnly|hasCode|hardwareAccelerated|icon|roundIcon|isGame|killAfterRestore|largeHeap|label|logo|manageSpaceActivity|name|networkSecurityConfig|permission|persistent|process|restoreAnyVersion|requiredAccountType|resizeableActivity|restrictedAccountType|supportsRtl|taskAffinity|testOnly|theme|uiOptions|usesCleartextTraffic|vmSafeMode)$/, // eslint-disable-line max-len
activity: /^(allowEmbedded|allowTaskReparenting|alwaysRetainTaskState|autoRemoveFromRecents|banner|clearTaskOnLaunch|configChanges|density|directBootAware|documentLaunchMode|enabled|excludeFromRecents|exported|finishOnTaskLaunch|hardwareAccelerated|icon|label|launchMode|maxRecents|multiprocess|name|noHistory|parentActivityName|permission|persistableMode|process|relinquishTaskIdentity|resizeableActivity|screenOrientation|showForAllUsers|stateNotNeeded|supportsPictureInPicture|taskAffinity|theme|uiOptions|windowSoftInputMode)$/, // eslint-disable-line max-len
application: /^(allowTaskReparenting|allowBackup|allowClearUserData|backupAgent|backupInForeground|banner|debuggable|description|directBootAware|enabled|extractNativeLibs|fullBackupContent|fullBackupOnly|hasCode|hardwareAccelerated|icon|isGame|killAfterRestore|largeHeap|label|logo|manageSpaceActivity|name|networkSecurityConfig|permission|persistent|process|restoreAnyVersion|requiredAccountType|resizeableActivity|restrictedAccountType|roundIcon|supportsRtl|taskAffinity|testOnly|theme|uiOptions|usesCleartextTraffic|vmSafeMode)$/, // eslint-disable-line max-len
activity: /^(allowEmbedded|allowTaskReparenting|alwaysRetainTaskState|autoRemoveFromRecents|banner|clearTaskOnLaunch|colorMode|configChanges|density|directBootAware|documentLaunchMode|enabled|excludeFromRecents|exported|finishOnTaskLaunch|hardwareAccelerated|icon|immersive|label|launchMode|lockTaskMode|maxRecents|maxAspectRatio|multiprocess|name|noHistory|parentActivityName|permission|persistableMode|process|relinquishTaskIdentity|resizeableActivity|screenOrientation|showForAllUsers|stateNotNeeded|supportsPictureInPicture|taskAffinity|theme|uiOptions|windowSoftInputMode)$/, // eslint-disable-line max-len
'activity-alias': /^(enabled|exported|icon|label|name|permission|targetActivity)$/,
data: /^(host|mimeType|path|pathPattern|pathPrefix|port|scheme)$/,
'intent-filter': /^(icon|label|priority)$/,
'meta-data': /^(name|resource|value)$/,
'path-permission': /^(path|pathPrefix|pathPattern|permission|readPermissions|writePermissions)$/,
provider: /^(authorities|enabled|exported|grantUriPermissions|icon|initOrder|label|multiprocess|name|permission|process|readPermission|syncable|writePermission)$/,
receiver: /^(enabled|exported|icon|label|name|permission|process)$/,
service: /^(enabled|exported|icon|isolatedProcess|label|name|permission|process)$/,
provider: /^(authorities|directBootAware|enabled|exported|grantUriPermissions|icon|initOrder|label|multiprocess|name|permission|process|readPermission|syncable|writePermission)$/,
receiver: /^(directBootAware|enabled|exported|icon|label|name|permission|process)$/,
service: /^(description|directBootAware|enabled|exported|icon|isolatedProcess|label|name|permission|process)$/,
'uses-library': /^(name|required)$/,
'uses-sdk': /^(name|required)$/
'uses-sdk': /^(maxSdkVersion|minSdkVersion|targetSdkVersion)$/
};

module.exports = AndroidManifest;
Expand Down Expand Up @@ -456,7 +456,36 @@ function AndroidManifest(filename) {
case 'uses-library':
this[tag][subtag] || (this[tag][subtag] = {});
Object.keys(src[tag][subtag]).forEach(function (key) {
this[tag][subtag][key] = src[tag][subtag][key];
// Copy source's XML tag attributes and child tags to target.
const nextSource = src[tag][subtag][key];
const nextTarget = this[tag][subtag][key];
if (!nextTarget) {
// Insert source's attribute or sub tag to target.
this[tag][subtag][key] = nextSource;
} else {
Object.keys(nextSource).forEach(function (subKey) {
if (subKey === 'configChanges') {
// For <activity/> "android:configChanges" attribute, only copy
// the attribute values from source that are missing in target.
const sourceArray = nextSource[subKey];
if (Array.isArray(sourceArray)) {
const targetArray = nextTarget[subKey];
if (Array.isArray(targetArray)) {
for (let nextValue of sourceArray) {
if (!targetArray.includes(nextValue)) {
targetArray.push(nextValue);
}
}
} else {
nextSource[subKey] = sourceArray;
}
}
} else {
// Overwrite target's attribute or sub tag with source.
nextTarget[subKey] = nextSource[subKey];
}
});
}
}, this);
break;
default:
Expand Down
25 changes: 18 additions & 7 deletions android/templates/build/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,31 @@
android:theme="@style/Theme.AppCompat<% if (tiapp.fullscreen || tiapp['statusbar-hidden'] || tiapp['navbar-hidden']) { %>.NoTitleBar<% if (tiapp.fullscreen || tiapp['statusbar-hidden']) { %>.Fullscreen<% } %><% } %>">

<activity android:name=".<%- classname %>Activity"
android:label="@string/app_name" android:theme="@style/Theme.Titanium"
android:alwaysRetainTaskState="true"
android:configChanges="keyboardHidden|orientation|fontScale|screenSize|smallestScreenSize|screenLayout">
android:configChanges="<%- allActivityConfigChanges %>"
android:label="@string/app_name"
android:theme="@style/Theme.Titanium"
android:alwaysRetainTaskState="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name="org.appcelerator.titanium.TiActivity"
android:configChanges="keyboardHidden|orientation|fontScale|screenSize|smallestScreenSize|screenLayout" />
<activity android:name="org.appcelerator.titanium.TiTranslucentActivity"
android:configChanges="keyboardHidden|orientation|fontScale|screenSize|smallestScreenSize|screenLayout"
<activity
android:name="org.appcelerator.titanium.TiActivity"
android:configChanges="<%- allActivityConfigChanges %>" />
<activity
android:name="org.appcelerator.titanium.TiTranslucentActivity"
android:configChanges="<%- allActivityConfigChanges %>"
android:theme="@style/Theme.Titanium.Translucent" />
<activity
android:name="ti.modules.titanium.media.TiCameraActivity"
android:configChanges="<%- allActivityConfigChanges %>"
android:theme="@style/Theme.AppCompat.Translucent.NoTitleBar.Fullscreen" />
<activity
android:name="ti.modules.titanium.media.TiVideoActivity"
android:configChanges="<%- allActivityConfigChanges %>"
android:theme="@style/Theme.AppCompat.Fullscreen" />
<activity android:name="ti.modules.titanium.ui.android.TiPreferencesActivity" />

<provider
Expand Down

0 comments on commit 2af5b74

Please sign in to comment.