Release Preview 20 shared desktop, Android and Sargo updates

Restore Gaia settings and media workflows, extend native network adapters, and align the APK payload. Generate French/English changelogs and website entries from the simplified JSON source.

Validation: shared JS and Tundra unit tests; desktop workflows and persistence; signed APK payload parity; Sargo RAM and installed startup with boot readback. Android runtime qualification is recorded separately.
This commit is contained in:
OverSu 2026-10-13 00:19:45 +02:00
commit 1311bed5e9
170 changed files with 19698 additions and 2495 deletions

View file

@ -1,5 +1,5 @@
import assert from 'node:assert/strict';
import {Activities} from '../services/activities.mjs';
import {Activities, isGaiaActivity} from '../services/activities.mjs';
const apps=[{id:'settings',origin:'http://settings.localhost'},{id:'wallpaper',origin:'http://wallpaper.localhost',manifest:{activities:{pick:{href:'/pick.html'}}}},{id:'ringtones',origin:'http://ringtones.localhost',manifest:{activities:{pick:{href:'/pick.html'}}}}];
const caller={app:apps[0],manager:{documentURI:{spec:'http://settings.localhost/#display'}}};
const picker={app:apps[1]}, stranger={app:apps[2]};
@ -81,3 +81,41 @@ const photoRequest=events.at(-1)[2].id;
assert.throws(()=>nested.finish(normalGallery,{id:photoRequest,result:{}}),/PERMISSION_DENIED/);
nested.finish(galleryPicker,{id:photoRequest,result:{blob:new Blob(['image'])}});
assert.equal(await (await photo).blob.text(),'image');
const music={id:'music',origin:'http://music.localhost',manifestURL:'http://music.localhost/manifest.webapp',manifest:{activities:{pick:{href:'/pick.html',filters:{type:['audio/*']}}}}};
const video={id:'video',origin:'http://video.localhost',manifestURL:'http://video.localhost/manifest.webapp',manifest:{activities:{pick:{href:'/index.html#pick',filters:{type:['video/*']}}}}};
apps.push(music,video);
for(const app of apps) app.manifestURL ||= app.origin+'/manifest.webapp';
gallery.manifest.activities.pick.filters.type.push('image/*');
phone.manifest.activities.pick.filters.type.value.push('text/vcard');
let menu;
const chooser=new Activities({identity:p=>p.app,apps:()=>apps,launch:(...a)=>launches.push(a),notify:(...a)=>events.push(a),uuid:()=>String(++serial),choose:data=>{menu=data;}});
const attachment={name:'pick',data:{type:['image/*','audio/*','video/*','text/vcard']}};
const attach=chooser.start(smsPeer,attachment);
assert.deepEqual(menu.choices.map(c=>c.manifest),[gallery,music,video,phone].map(a=>a.manifestURL));
assert.throws(()=>chooser.select(smsPeer,{id:menu.id,value:0}),/PERMISSION_DENIED/);
assert.throws(()=>chooser.select({app:{id:'system'}},{id:menu.id,value:99}),/INVALID_ACTIVITY_CHOICE/);
chooser.select({app:{id:'system'}},{id:menu.id,value:'0'});
const peer={app:gallery,manager:{documentURI:{spec:launches.at(-1)[1]}}};
chooser.subscribe(peer);
chooser.finish(peer,{id:events.at(-1)[2].id,result:{blob:new Blob(['picked']),name:'photo.jpg'}});
assert.equal((await attach).name,'photo.jpg');
const cancelChoice=chooser.start(smsPeer,attachment);
chooser.select({app:{id:'system'}},{id:menu.id,value:-1});await assert.rejects(cancelChoice,/ACTIVITY_CANCELLED/);
const closed=chooser.start(smsPeer,attachment);chooser.cleanup(smsPeer);await assert.rejects(closed,/ACTIVITY_CANCELLED/);
console.log('SMS attachment chooser: four original providers, ownership, cancellation and result: OK');
const tones={id:'ringtones',origin:'http://ringtones.localhost',manifest:{activities:{configure:{href:'/manage.html',filters:{target:{required:true,pattern:'ringtone'}},disposition:'inline'}}}};
apps[2].manifest.activities.configure=tones.manifest.activities.configure;
assert.equal(isGaiaActivity({name:'configure',data:{target:'ringtone'}}),true);
assert.equal(broker.route({name:'configure',data:{target:'ringtone'}}).url.pathname,'/manage.html');
assert.throws(()=>broker.route({name:'configure',data:{target:'other'}}),/ACTIVITY_NOT_SUPPORTED/);
apps[0].manifest={launch_path:'/index.html'};
const keyboardPeer={app:{id:'keyboard'}};
const configure={name:'moz_configure_window',data:{target:'device'}};
assert.equal(isGaiaActivity(configure),true);
await broker.start(keyboardPeer,configure);
assert.equal(launches.at(-1)[0].id,'settings');
assert.equal(broker.pending.size,0);
assert.throws(()=>broker.start({app:{id:'untrusted'}},configure),/PERMISSION_DENIED/);
console.log('Keyboard returns to Settings without a pending activity: OK');