VulpesOS/gaia/apps/communications/contacts/js/activities.js
OverSu 0041cd9660 Update shared Gaia workflows and Sargo development tooling
Fix camera switching and Contacts/SMS activity routing across shared hosts.
Integrate Marketplace installation and native standalone recovery tools,
Bluetooth runtime packages and trusted certificates.

Update English/French READMEs and the shared changelog with tested scope
and remaining radio/audio/Bluetooth limitations. Preview17 remains the
existing APK; no new native image is released.

Validation: shared JavaScript and native Python tests; Android packaging
and payload parity from an isolated publication checkout.
2026-10-08 14:27:17 +02:00

374 lines
11 KiB
JavaScript
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* globals ConfirmDialog, Contacts, LazyLoader, utils, ActionMenu, HeaderUI,
ContactToVcardBlob, VcardFilename, VcardActivityHandler, MainNavigation */
/* exported ActivityHandler */
'use strict';
/**
* Per RFC 6350, text/vcard is the canonical MIME media type for vCards, but
* there are also deprecated types as well. Whenever we disambiguate what an
* activity is requesting based on its MIME media type, we need to check if it
* is any of these, and not just text/vcard.
*/
const VCARD_MIME_TYPES = [
'text/vcard',
'text/x-vcard',
'text/directory'
];
var ActivityHandler = {
_currentActivity: null,
_launchedAsInlineActivity: new URLSearchParams(window.location.search).has('pick'),
mozContactParam: null,
_actionMenu: null,
get currentlyHandling() {
return !!this._currentActivity;
},
get activityName() {
if (!this._currentActivity) {
return null;
}
return this._currentActivity.source.name;
},
get activityDataType() {
if (!this._currentActivity) {
return null;
}
return this._currentActivity.source.data.type;
},
get activityData() {
if (!this._currentActivity) {
return null;
}
return this._currentActivity.source.data;
},
get activityContactProperties() {
if (!this._currentActivity) {
return null;
}
return this._currentActivity.source.data.contactProperties;
},
/* checks first if we are handling an activity, then if it is
* of the same type of any of the items from the list provided.
* @param list Array with types of activities to be checked
*/
currentActivityIs: function(list) {
return this.currentlyHandling && list.indexOf(this.activityName) !== -1;
},
/* checks first if we are handling an activity, then checks that
* it is NOT of the same type of any of the items from the list provided.
* @param list Array with types of activities to be checked
*/
currentActivityIsNot: function(list) {
return this.currentlyHandling && list.indexOf(this.activityName) === -1;
},
isCancelable: function() {
return new Promise(resolve => {
resolve(!!this.currentlyHandling);
});
},
launch_activity: function ah_launch(activity, action) {
if (this._launchedAsInlineActivity) {
return;
}
this._currentActivity = activity;
this.isCancelable().then(isCancelable => {
HeaderUI.updateHeader(isCancelable);
});
var hash = action;
var param, params = [];
if (activity.source &&
activity.source.data &&
activity.source.data.params) {
var originalParams = activity.source.data.params;
for (var i in originalParams) {
param = originalParams[i];
params.push(i + '=' + param);
}
hash += '?' + params.join('&');
}
document.location.hash = hash;
},
handle: function ah_handle(activity) {
const VCARD_DEPS = '/contacts/js/activities_vcard.js';
switch (activity.source.name) {
case 'new':
this.launch_activity(activity, 'view-contact-form');
break;
case 'import':
case 'open':
LazyLoader.load(VCARD_DEPS, () => {
VcardActivityHandler.handle(activity, this);
});
break;
case 'update':
this.launch_activity(activity, 'add-parameters');
break;
case 'pick':
if (window.VulpesCompat) this._launchedAsInlineActivity = true;
if (!this._launchedAsInlineActivity) {
return;
}
this._currentActivity = activity;
this.isCancelable().then(isCancelable => {
HeaderUI.updateHeader(isCancelable);
});
MainNavigation.home();
break;
}
},
importContactsFromFile: function ah_importContactFromVcard(activity) {
var self = this;
if (activity.source &&
activity.source.data &&
activity.source.data.blob) {
LazyLoader.load([
document.querySelector('#loading-overlay'),
'/shared/js/contacts/import/utilities/import_from_vcard.js',
'/shared/js/contacts/import/utilities/overlay.js'
], function loaded() {
Contacts.loadFacebook(function() {
utils.importFromVcard(activity.source.data.blob,
function imported(numberOfContacts, id) {
if (numberOfContacts === 1) {
activity.source.data.params = {id: id};
self.launch_activity(activity, 'view-contact-details');
} else {
self.launch_activity(activity, 'view-contact-list');
}
}
);
});
});
} else {
this._currentActivity.postError('wrong parameters');
this._currentActivity = null;
}
},
dataPickHandler: function ah_dataPickHandler(theContact) {
var type, dataSet, noDataStr;
var result = {};
var self = this;
// Keeping compatibility with previous implementation. If
// we want to get the full contact, just pass the parameter
// 'fullContact' equal true.
if (this.activityDataType === 'webcontacts/contact' &&
this.activityData.fullContact === true) {
result = utils.misc.toMozContact(theContact);
this.postPickSuccess(result);
return;
}
// Was this a request for vCard export as a blob? Check all supported MIME
// types.
var isVcardDataType = VCARD_MIME_TYPES.some((mime) => {
return this.activityDataType.indexOf(mime) !== -1;
});
if (isVcardDataType) {
// Normalize the type to text/vcard so other places that check MIME types
// (ex: the Facebook guards) experience a consistent MIME type.
this._currentActivity.source.data.type = 'text/vcard';
LazyLoader.load([
'/shared/js/text_normalizer.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js'
], function lvcard() {
ContactToVcardBlob([theContact], function blobReady(vcardBlob) {
VcardFilename(theContact).then(filename => {
self.postPickSuccess({
name: filename,
blob: vcardBlob
});
});
}, {
// Some MMS gateways prefer this MIME type for vcards
type: 'text/x-vcard'
});
});
return;
}
switch (this.activityDataType) {
case 'webcontacts/tel':
type = 'contact';
dataSet = theContact.tel;
noDataStr = 'no_contact_phones';
break;
case 'webcontacts/contact':
type = 'number';
dataSet = theContact.tel;
noDataStr = 'no_contact_phones';
break;
case 'webcontacts/email':
type = 'email';
dataSet = theContact.email;
noDataStr = 'no_contact_email';
break;
case 'webcontacts/select':
type = 'select';
var data = [];
if (this.activityContactProperties.indexOf('tel') !== -1) {
if (theContact.tel && theContact.tel.length) {
data = data.concat(theContact.tel);
}
}
if (this.activityContactProperties.indexOf('email') !== -1) {
if (theContact.email && theContact.email.length) {
data = data.concat(theContact.email);
}
}
dataSet = data;
noDataStr = 'no_contact_data_available';
break;
}
var hasData = dataSet && dataSet.length;
var numOfData = hasData ? dataSet.length : 0;
result.name = theContact.name;
switch (numOfData) {
case 0:
// If no required type of data
var dismiss = {
title: 'ok',
callback: function() {
ConfirmDialog.hide();
}
};
ConfirmDialog.show(null, noDataStr, dismiss);
break;
case 1:
// if one required type of data
if (this.activityDataType == 'webcontacts/tel' ||
this.activityDataType == 'webcontacts/select') {
result = this.pickContactsResult(theContact);
} else {
result[type] = dataSet[0].value;
}
this.postPickSuccess(result);
break;
default:
// if more than one required type of data
LazyLoader.load('/contacts/js/action_menu.js', function() {
if (!self._actionMenu) {
self._actionMenu = new ActionMenu();
} else {
// To be sure that the action menu is empty
self._actionMenu.hide();
}
var itemData;
var capture = function(itemData) {
return function() {
if (self.activityDataType == 'webcontacts/tel' ||
self.activityDataType == 'webcontacts/select') {
result = self.pickContactsResult(theContact, itemData);
} else {
result[type] = itemData;
}
self._actionMenu.hide();
self.postPickSuccess(result);
};
};
for (var i = 0, l = dataSet.length; i < l; i++) {
itemData = dataSet[i].value;
var carrier = dataSet[i].carrier || '';
self._actionMenu.addToList(
{
id: 'pick_destination',
args: {destination: itemData, carrier: carrier}
},
capture(itemData)
);
}
self._actionMenu.show();
});
} // switch
},
pickContactsResult:
function ah_pickContactsResult(theContact, itemData) {
var pickResult = {};
var contact = utils.misc.toMozContact(theContact);
if (this.activityDataType == 'webcontacts/tel') {
pickResult = contact;
if (itemData) {
pickResult.tel = this.filterDestinationForActivity(
itemData, pickResult.tel);
}
} else if (this.activityDataType == 'webcontacts/select') {
pickResult.contact = contact;
if (!itemData) {
pickResult.select = pickResult.contact.tel;
if (!pickResult.select || !pickResult.select.length) {
pickResult.select = pickResult.contact.email;
}
} else {
pickResult.select = this.filterDestinationForActivity(
itemData, pickResult.contact.tel);
if (!pickResult.select || !pickResult.select.length) {
pickResult.select = this.filterDestinationForActivity(
itemData, pickResult.contact.email);
}
}
}
return pickResult;
},
filterDestinationForActivity:
function ah_filterDestinationForActivity(itemData, dataSet) {
return dataSet.filter(function isSamePhone(item) {
return item.value == itemData;
});
},
postNewSuccess: function ah_postNewSuccess(contact) {
this._currentActivity.postResult({ contact: contact });
this._currentActivity = null;
if (window.VulpesCompat) this._launchedAsInlineActivity = false;
},
postPickSuccess: function ah_postPickSuccess(result) {
this._currentActivity.postResult(result);
this._currentActivity = null;
if (window.VulpesCompat) this._launchedAsInlineActivity = false;
},
postCancel: function ah_postCancel() {
this._currentActivity.postError('canceled');
this._currentActivity = null;
if (window.VulpesCompat) this._launchedAsInlineActivity = false;
}
};