Release Android Preview 21 and bilingual changelog
Add permission-based reading of Android contacts, SMS and call history. Include shared Gaia fixes and document device validation limits. Pixel image qualification remains pending.
This commit is contained in:
parent
1c20a0b6d5
commit
200d1fb235
43 changed files with 2271 additions and 65 deletions
85
tests/android-personal-ui.test.mjs
Normal file
85
tests/android-personal-ui.test.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import vm from 'node:vm';
|
||||
import {readFileSync} from 'node:fs';
|
||||
function setup(app,path,call){
|
||||
const events=new Map(),notices=[];
|
||||
const context={console,URL,Map,Set,Number,JSON,Date,Promise,queueMicrotask,setTimeout,clearTimeout,setInterval,clearInterval,
|
||||
location:{hostname:app+'.localhost',pathname:path},navigator:{language:'en'},localStorage:{removeItem(){}},
|
||||
document:{hidden:false,documentElement:{lang:'en'},readyState:'complete',addEventListener:(type,fn)=>events.set('document-'+type,fn),querySelectorAll(){return[];}},
|
||||
MutationObserver:class{observe(){}},addEventListener:(type,fn)=>events.set(type,fn),
|
||||
VulpesCompat:{call},VulpesGaiaUI:{notice:value=>notices.push(value)}};
|
||||
context.window=context;vm.runInNewContext(readFileSync('android/ui/android-ui.js','utf8'),context);
|
||||
return {context,events,notices};
|
||||
}
|
||||
test('native call rows use Gaia grouping and expose details without changing the local DB',async()=>{
|
||||
const s=setup('communications','/dialer/index.html',async(_method,p)=>p.operation==='status'?{calls:true}:[
|
||||
{_id:1,date:1000,number:'5550199',duration:42,type:2},
|
||||
{_id:2,date:2000,number:'5550199',duration:0,type:2}]);
|
||||
let localReads=0;
|
||||
s.context.CallLogDBManager={getGroupList(){localReads++;},getGroup(){localReads++;},getGroupAtPosition(){localReads++;},
|
||||
_getGroupId:r=>[0,r.number,r.type],_getGroupObject:g=>({...g,id:g.id.join('-'),type:g.id[2]})};
|
||||
const db=s.context.CallLogDBManager;
|
||||
const rows=await new Promise(resolve=>db.getGroupList(resolve,'lastEntryDate',true,false));
|
||||
assert.equal(rows.length,1);assert.equal(rows[0].retryCount,2);assert.equal(rows[0].calls[1].duration,42000);
|
||||
assert.equal((await db.getGroup('5550199',1000,'dialing')).calls.length,2);assert.equal(localReads,0);
|
||||
const streamed=[];
|
||||
await new Promise(resolve=>db.getGroupList(c=>{if(!c.value)return resolve();streamed.push(c.value);c.continue();},'lastEntryDate',true,true));
|
||||
assert.equal(streamed.length,1);
|
||||
});
|
||||
test('SMS provider change refreshes the open conversation without reloading or touching the draft',async()=>{
|
||||
const inserted=[];
|
||||
const s=setup('sms','/index.html',async()=>[{id:-1,threadId:-2,body:'Received',timestamp:1000}]);
|
||||
s.context.Navigation={isCurrentPanel:name=>name==='thread'};
|
||||
s.context.ConversationView={activeThread:{id:-2},appendMessage:async message=>inserted.push(message)};
|
||||
s.context.Compose={getText:()=> 'Unsent draft'};
|
||||
s.context.document.getElementById=()=>null;
|
||||
s.context.location.reload=()=>{throw Error('draft lost');};
|
||||
s.events.get('vulpes-service-event')({detail:{type:'android-data-changed',data:{kind:'sms'}}});
|
||||
await new Promise(resolve=>setImmediate(resolve));
|
||||
assert.equal(inserted.length,1);assert.equal(inserted[0].body,'Received');
|
||||
assert.equal(s.context.Compose.getText(),'Unsent draft');
|
||||
});
|
||||
|
||||
test('hidden call log refreshes contact names when shown after Android access changes',()=>{
|
||||
const s=setup('communications','/dialer/index.html',async()=>({}));
|
||||
let updates=0;
|
||||
s.context.CallLog={sticky:{},_contactCache:true,updateListWithContactInfo(){updates++;}};
|
||||
s.context.document.hidden=true;
|
||||
s.events.get('vulpes-service-event')({detail:{type:'android-data-changed',data:{kind:'contacts'}}});
|
||||
assert.equal(updates,0);
|
||||
s.context.document.hidden=false;
|
||||
s.events.get('visibilitychange')();
|
||||
assert.equal(updates,1);
|
||||
assert.equal(s.context.CallLog._contactCache,false);
|
||||
});
|
||||
|
||||
test('Android contact HTML caches are disabled before Gaia can apply stale names',async()=>{
|
||||
const s=setup('communications','/contacts/index.html',async()=>({}));
|
||||
try {
|
||||
let writes=0;
|
||||
s.context.Cache={get enabled(){return true;},get active(){return true;},set firstChunk(value){writes++;},apply:async()=> 'stale names'};
|
||||
assert.equal(s.context.Cache.enabled,false);
|
||||
assert.equal(s.context.Cache.active,false);
|
||||
s.context.Cache.firstChunk={cache:'Android names'};
|
||||
assert.equal(writes,0);
|
||||
await assert.rejects(s.context.Cache.apply());
|
||||
} finally{s.events.get('pagehide')();}
|
||||
});
|
||||
|
||||
test('revoked native contact details close when the hidden app becomes visible',async()=>{
|
||||
const s=setup('communications','/contacts/index.html',async()=>[]);
|
||||
try {
|
||||
let closed=0;
|
||||
s.context.MainNavigation={currentView:()=> 'view-contact-details'};
|
||||
s.context.Contacts={goBack(){closed++;}};
|
||||
s.events.get('document-click')({target:{closest:()=>({dataset:{uuid:'android:1'}})}});
|
||||
s.context.document.hidden=true;
|
||||
s.events.get('vulpes-service-event')({detail:{type:'android-data-changed',data:{kind:'contacts'}}});
|
||||
assert.equal(closed,0);
|
||||
s.context.document.hidden=false;
|
||||
s.events.get('visibilitychange')();
|
||||
await new Promise(resolve=>setImmediate(resolve));
|
||||
assert.equal(closed,1);
|
||||
} finally{s.events.get('pagehide')();}
|
||||
});
|
||||
Loading…
Reference in a new issue