From fc06fdb57e9b88d9fe622ca8c47938716f363239 Mon Sep 17 00:00:00 2001 From: Johan Lorenzo Date: Fri, 25 Sep 2020 18:49:29 +0200 Subject: [PATCH 01/30] Bug 1667367 - Change nightly schedule to align with GV and AC nightlies Fixes https://github.com/mozilla-mobile/android-components/pull/8508 --- .cron.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.cron.yml b/.cron.yml index 0ee4b916a..3e9ae015f 100644 --- a/.cron.yml +++ b/.cron.yml @@ -10,7 +10,7 @@ jobs: treeherder-symbol: Nd target-tasks-method: nightly when: - - {hour: 18, minute: 0} + - {hour: 5, minute: 0} # This is a temporary hook in order to not overload Google Play. # See bug 1628413 for more context. - name: nightly-on-google-play @@ -19,7 +19,7 @@ jobs: treeherder-symbol: Nd-gp target-tasks-method: nightly-on-google-play when: - - {hour: 6, minute: 0} + - {hour: 17, minute: 0} - name: fennec-production job: type: decision-task @@ -31,7 +31,7 @@ jobs: type: decision-task treeherder-symbol: bump-ac target-tasks-method: bump_android_components - when: [{hour: 14, minute: 0}] + when: [{hour: 15, minute: 30}] - name: screenshots job: type: decision-task From 6289da892c5386040bdf1adf6149c6040043b2fb Mon Sep 17 00:00:00 2001 From: mcarare Date: Fri, 25 Sep 2020 15:31:22 +0300 Subject: [PATCH 02/30] For #15310: Also catch ActivityNotFoundException when sharing to app. --- .../org/mozilla/fenix/share/ShareController.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt index 2c1f44888..e3c1a7310 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.share +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.Intent.ACTION_SEND @@ -98,13 +99,19 @@ class DefaultShareController( setClassName(app.packageName, app.activityName) } + @Suppress("TooGenericExceptionCaught") val result = try { context.startActivity(intent) ShareController.Result.SUCCESS - } catch (e: SecurityException) { - snackbar.setText(context.getString(R.string.share_error_snackbar)) - snackbar.show() - ShareController.Result.SHARE_ERROR + } catch (e: Exception) { + when (e) { + is SecurityException, is ActivityNotFoundException -> { + snackbar.setText(context.getString(R.string.share_error_snackbar)) + snackbar.show() + ShareController.Result.SHARE_ERROR + } + else -> throw e + } } dismiss(result) } From daced89387e5b85024aa26cd303eb13b5e7a51b1 Mon Sep 17 00:00:00 2001 From: mcarare Date: Mon, 28 Sep 2020 12:40:29 +0300 Subject: [PATCH 03/30] For #15310: Add test for ActivityNotFoundException when sharing to app. --- .../fenix/share/ShareControllerTest.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt index ace841db6..90540b24c 100644 --- a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.share import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import androidx.navigation.NavController @@ -141,6 +142,31 @@ class ShareControllerTest { } } + @Test + fun `handleShareToApp should dismiss with an error start when a ActivityNotFoundException occurs`() { + val appPackageName = "package" + val appClassName = "activity" + val appShareOption = AppShareOption("app", mockk(), appPackageName, appClassName) + val shareIntent = slot() + // Our share Intent uses `FLAG_ACTIVITY_NEW_TASK` but when resolving the startActivity call + // needed for capturing the actual Intent used the `slot` one doesn't have this flag so we + // need to use an Activity Context. + val activityContext: Context = mockk() + val testController = DefaultShareController(activityContext, shareSubject, shareData, mockk(), + snackbar, mockk(), mockk(), testCoroutineScope, dismiss) + every { activityContext.startActivity(capture(shareIntent)) } throws ActivityNotFoundException() + every { activityContext.getString(R.string.share_error_snackbar) } returns "Cannot share to this app" + + testController.handleShareToApp(appShareOption) + + verifyOrder { + activityContext.startActivity(shareIntent.captured) + snackbar.setText("Cannot share to this app") + snackbar.show() + dismiss(ShareController.Result.SHARE_ERROR) + } + } + @Test @Suppress("DeferredResultUnused") fun `handleShareToDevice should share to account device, inform callbacks and dismiss`() { From d287e6e9e06d6cc87bb9b2dad109e92ed364215f Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Mon, 28 Sep 2020 00:06:16 +0000 Subject: [PATCH 04/30] Import l10n. --- app/src/main/res/values-br/strings.xml | 47 ++++++++++++++++++--- app/src/main/res/values-cak/strings.xml | 32 ++++++++++++++- app/src/main/res/values-co/strings.xml | 7 ++++ app/src/main/res/values-el/strings.xml | 5 +++ app/src/main/res/values-es-rES/strings.xml | 7 ++++ app/src/main/res/values-eu/strings.xml | 29 ++++++++----- app/src/main/res/values-fr/strings.xml | 7 ++++ app/src/main/res/values-fy-rNL/strings.xml | 7 ++++ app/src/main/res/values-hr/strings.xml | 7 ++++ app/src/main/res/values-in/strings.xml | 46 ++++++++++++++++++--- app/src/main/res/values-it/strings.xml | 8 ++++ app/src/main/res/values-kab/strings.xml | 14 ++++++- app/src/main/res/values-lt/strings.xml | 11 +++++ app/src/main/res/values-nl/strings.xml | 7 ++++ app/src/main/res/values-pl/strings.xml | 7 ++++ app/src/main/res/values-pt-rPT/strings.xml | 7 ++++ app/src/main/res/values-ru/strings.xml | 9 ++++ app/src/main/res/values-sq/strings.xml | 11 +++++ app/src/main/res/values-sr/strings.xml | 7 ++++ app/src/main/res/values-tg/strings.xml | 3 ++ app/src/main/res/values-ur/strings.xml | 48 ++++++++++++++++------ 21 files changed, 290 insertions(+), 36 deletions(-) diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index 6eca7734d..f469f0200 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -140,6 +140,8 @@ Staliañ Ivinelloù goubredet + + Adgoubredañ Kavout er bajennad @@ -260,6 +262,8 @@ Digeriñ an ereoù en un ivinell brevez Aotren an tapadennoù-skramm er merdeiñ prevez + + Mard eo aotreet, an ivinelloù prevez a vo hewel pa vo meur a arload digor Ouzhpennañ ur verradenn merdeiñ prevez @@ -280,6 +284,8 @@ Neuz Degemer + + Jestroù Personelaat @@ -314,8 +320,12 @@ Klask er roll istor Klask er sinedoù + + Klask en ivinelloù goubredet Arventennoù ar gont + + Leuniañ an ereoù ent emgefreek Digeriñ ereoù en arloadoù @@ -452,6 +462,16 @@ Mont gant neuz ar benveg + + + Sachit da azgrenaat + + Dibunit da guzhat ar varrenn-ostilhoù + + Riklit ar varrenn-ostilhoù war ar c’hostez evit cheñch ivinell + + Rikli ar ar varrenn-ostilhoù war-zu an nec’h evit digeriñ ivinelloù + Estezioù @@ -1039,7 +1059,7 @@ - Kennasket hoc’h evel %s war ur merdeer Firefox all war ar pellgomzer-mañ. Fellout a ra deoc’h kennaskañ gant ar gont-mañ? + Kennasket oc’h evel %s war ur merdeer Firefox all war an trevnad-mañ. Fellout a ra deoc’h kennaskañ gant ar gont-mañ? Ya, kennaskit ac’hanon @@ -1129,6 +1149,8 @@ Kennaskit gant ho kamera Ober gant ur chomlec’h postel kentoc’h + + Krouit unan evit goubredañ Firefox etre an trevnadoù.]]> Firefox a baouezo da c’houbredañ gant ho kont, met ne vo ket dilamet ho roadennoù merdeiñ war an trevnad-mañ. @@ -1237,6 +1259,11 @@ The first parameter is the app name --> %s | Levraouegoù digor o zarzh + + Heulierien adheñchañ + + Skarzhañ an toupinoù lakaet gant adheñchadurioù etrezek lec’hiennoù heuliañ anavezet + Skor @@ -1279,8 +1306,9 @@ Kenderc’hel etrezek al lec’hienn Anv ar verradenn + - Gallout a rit ouzhpennañ al lec’hienn d’ho pennbajenn en un doare aes evit gallout he haeziñ ha merdeiñ buanoc’h, evel un arload. + Gallout a rit ouzhpennañ al lec’hienn-mañ da bennbajenn ho trevnad evit mont war-eeun ha merdeiñ primoc’h evel ma vefe un arload. Titouroù kennaskañ @@ -1351,8 +1379,12 @@ Eilet eo bet al lec’hienn er golver Eilañ ar ger-tremen + + Skarzhañ ar ger-tremen Eilañ an anv arveriad + + Skarzhañ an anv arveriad Eilañ al lec’hienn @@ -1505,7 +1537,7 @@ Un titour kennaskañ gant an anv arveriad-mañ a zo dioutañ endeo. - + Kennaskañ un trevnad all. Adkennaskit mar plij ganeoc’h. @@ -1526,7 +1558,7 @@ Tizhet eo bet ar vevenn lec’hiennoù - Evit ouzhpennañ ul lec’hienn gwellañ nevez e rankit dilemel unan all. Stokit ’pad pell amzer war ul lec’hienn ha dibabit Dilemel. + Evit ouzhpennañ ul lec’hienn gwellañ eo ret deoc’h dilemel unan. Stokit ha dalc’hit al lec’hienn ha dibabit dilemel. Mat, komprenet am eus @@ -1536,7 +1568,7 @@ Dilemel - Tennit gounid eus %s. @@ -1544,4 +1576,9 @@ Dastumit ar pezh a zo pouezus evidoc‘h Strollit ar cʼhlaskoù, al lecʼhiennoù hag an ivinelloù heñvel evit mont daveto buanocʼh. + + Kennasket hoc’h evel %s war ur merdeer Firefox all war ar pellgomzer-mañ. Fellout a ra deoc’h kennaskañ gant ar gont-mañ? + + Gallout a rit ouzhpennañ al lec’hienn d’ho pennbajenn en un doare aes evit gallout he haeziñ ha merdeiñ buanoc’h, evel un arload. + diff --git a/app/src/main/res/values-cak/strings.xml b/app/src/main/res/values-cak/strings.xml index 56f6e63f7..096be2033 100644 --- a/app/src/main/res/values-cak/strings.xml +++ b/app/src/main/res/values-cak/strings.xml @@ -151,6 +151,8 @@ Tiyak Ximon taq ruwi\' + + Tixim chik Tikanöx pa ruxaq @@ -277,6 +279,8 @@ Tiya\' q\'ij richin nichap ruwa pa ichinan okem pa k\'amaya\'l + + We ya\'on q\'ij, xketz\'et chuqa\' ri ichinan taq ruwi\' toq e jaqäl k\'ïy chokoy Titz\'aqatisäx choj okem pa ri ichinan okem pa k\'amaya\'l @@ -297,6 +301,8 @@ Wachinel Tikirib\'äl + + B\'anoj paläj Tichinäx @@ -334,8 +340,12 @@ Tikanöx runatab\'al okem pa k\'amaya\'l Kekanöx taq yaketal + + Kekanöx ri taq ruwi\' eximon Kinuk\'ulem Rub\'i\' Taqoya\'l + + Titz\'aqatisäx ruyon URLs Kejaq taq ximonel pa taq chokoy @@ -476,6 +486,17 @@ Tojqäx ri ruwachinel oyonib\'äl + + + Tiqirirëx richin nik\'ex + + Taq\'axaj richin nawewaj ri ajkajtz\'ik + + + Tiq\'axäx ri ajkajtz\'ik chi taq ruchi\' richin nijal ruwi\' + + Tiq\'axäx ri ajkatz\'ik ajsik richin yejaq taq ruwi\' + Taq Moloj @@ -1174,6 +1195,8 @@ Tatikirisaj molojri\'ïl rik\'in ri elesäy awachib\'al Tawokisaj ri taqoya\'l + + Tatz\'uku\' jun richin naxïm Firefox pa taq okisab\'äl.]]> Firefox man xtuxïm ta chik ri rub\'i\' ataqoya\'l, man xkeyuj ta ri taq rutzij awokem pa re oyonib\'äl re\'. @@ -1286,6 +1309,11 @@ The first parameter is the app name --> %s | OSS taq wujb\'äl + + Kechojmïx chik Ojqanela\' + + Yeruyüj ri taq cookies echojmirisan pan ajk\'amaya\'l taq ruxaq ojqanem etaman kiwa. + Tob\'äl @@ -1589,6 +1617,8 @@ Achi\'el: \nhttps://www.google.com/search?q=%s Xaq\'i\' ruchi\' ri jutaqil taq ruxaq + + Richin natz\'aqatisaj jun k\'ak\'a\' nimaläj ruxaq, tayuju\' jun. Tapitz\'a\' ri ruxaq chuqa\' tacha\' tiyuj. ÜTZ, Wetaman Chik @@ -1598,7 +1628,7 @@ Achi\'el: \nhttps://www.google.com/search?q=%s Tiyuj - Ütz tawokisaj ri %s. diff --git a/app/src/main/res/values-co/strings.xml b/app/src/main/res/values-co/strings.xml index e73cb750e..a981b1d43 100644 --- a/app/src/main/res/values-co/strings.xml +++ b/app/src/main/res/values-co/strings.xml @@ -1174,6 +1174,8 @@ Cunnettatevi cù u vostru appareghju-fotò Impiegà piuttostu un messaghju elettronicu + + Createne unu per sincrunizà i vostri apparechji.]]> Firefox ùn si sincruniserà più cù u vostru contu, ma ùn squasserà alcunu datu di navigazione nant’à st’apparechju. @@ -1281,6 +1283,11 @@ The first parameter is the app name --> %s | Bibliuteche di fonte aperta + + Frattighjatori da ridirezzione + + Squasseghja i canistrelli definiti da ridirezzione ver di siti web cunnisciuti per u spiunagiu. + Assistenza diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index adced9d57..5312f601a 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1141,6 +1141,8 @@ Χρήση email + + Δημιουργήστε έναν για συγχρονισμό του Firefox μεταξύ συσκευών.]]> Το Firefox θα σταματήσει να συγχρονίζεται με το λογαριασμό σας, αλλά δεν θα διαγράψει τα δεδομένα περιήγησης από αυτή τη συσκευή. @@ -1242,6 +1244,9 @@ The first parameter is the app name --> %s | Βιβλιοθήκες OSS + + Ανακατεύθυνση ιχνηλατών + Υποστήριξη diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 80fd96bb9..ac04cdc7e 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -1196,6 +1196,8 @@ Inicia sesión con tu cámara Usa el correo electrónico + + Crea una para sincronizar Firefox entre dispositivos.]]> Firefox dejará de sincronizarse con tu cuenta, pero no eliminará ninguno de tus datos de navegación en este dispositivo. @@ -1305,6 +1307,11 @@ The first parameter is the app name --> %s | Bibliotecas OSS + + Rastreadores de redirección + + Borra las cookies establecidas por redirecciones a sitios web de rastreo conocidos. + Ayuda diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 1bee33194..695136fad 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -147,6 +147,8 @@ Instalatu Sinkronizatutako fitxak + + Sinkronizatu berriro Bilatu orrian @@ -292,6 +294,8 @@ Itxura Hasierako pantaila + + Keinuak Pertsonalizatu @@ -326,9 +330,13 @@ Bilatu nabigatze-historia Bilatu laster-markak + + Bilatu sinkronizatutako fitxak Kontu-ezarpenak + + Osatu automatikoki URLak Ireki loturak aplikazioetan @@ -471,6 +479,9 @@ Jarraitu gailuaren itxura + + Korritu tresna-barra ezkutatzeko + Saioak @@ -1061,10 +1072,6 @@ Hasi sinkronizatzen laster-markak, historia eta gehiago zure Firefox kontua erabiliz. Argibide gehiago - - %s bezala saioa hasita duzu telefono honetako beste Firefox nabigatzaile batean. Kontu honekin saioa hasi nahi duzu? Bai, hasi saioa @@ -1308,9 +1315,6 @@ Lasterbidearen izena - - Modu errazean gehi dezakezu webgune hau zure telefonoaren hasierako pantailan berehalako sarbidea izan eta aplikazio-moduko esperientziarekin azkarrago nabigatzeko. - Saio-hasierak eta pasahitzak @@ -1537,7 +1541,7 @@ Erabiltzaile-izen hori badago lehendik ere - + Konektatu beste gailu bat. Autentifikatu berriro mesedez. @@ -1557,8 +1561,6 @@ Gune erabilienen mugara iritsi da - - Gune erabilienetako bat gehitzeko, kendu aurretik dagoen bat. Sakatu eta mantendu gunea eta hautatu kentzeko aukera. Ados, ulertuta @@ -1568,7 +1570,7 @@ Kendu - Atera %s(r)i ahalik eta zuku gehiena. @@ -1576,4 +1578,9 @@ Bildu zuretzat garrantzizkoa dena Multzokatu antzerako bilaketak, guneak eta fitxak sarbide azkarrago baterako. + + %s bezala saioa hasita duzu telefono honetako beste Firefox nabigatzaile batean. Kontu honekin saioa hasi nahi duzu? + + Modu errazean gehi dezakezu webgune hau zure telefonoaren hasierako pantailan berehalako sarbidea izan eta aplikazio-moduko esperientziarekin azkarrago nabigatzeko. + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 33fde7483..2fa062c0a 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1199,6 +1199,8 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n Connectez-vous avec votre appareil photo Utiliser plutôt une adresse électronique + + Créez-en un pour synchroniser Firefox entre vos appareils.]]> Firefox ne se synchronisera plus avec votre compte, mais ne supprimera aucune de vos données de navigation sur cet appareil. @@ -1306,6 +1308,11 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n The first parameter is the app name --> %s | Bibliothèques open source + + Traqueurs par redirection + + Efface les cookies définis par redirection vers des sites web connus pour le pistage. + Assistance diff --git a/app/src/main/res/values-fy-rNL/strings.xml b/app/src/main/res/values-fy-rNL/strings.xml index a1739464d..508ddb415 100644 --- a/app/src/main/res/values-fy-rNL/strings.xml +++ b/app/src/main/res/values-fy-rNL/strings.xml @@ -1163,6 +1163,8 @@ Meld jo oan mei jo kamera E-mail brûke + + Meitsje der ien oan om Firefox tusken apparaten te syngronisearjen.]]> Firefox stoppet de syngronisaasje mei jo account, mar sil gjin sneupgegevens op dit apparaat fuortsmite. @@ -1271,6 +1273,11 @@ The first parameter is the app name --> %s | OSS-biblioteken + + Trochliedingstrackers + + Wisket cookies dy\'t ynsteld binne troch trochliedingen nei bekende trackingwebsites. + Stipe diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 5663499dc..735e86af6 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1171,6 +1171,8 @@ Prijavi se pomoću kamere Umjesto toga koristi e-poštu + + Stvori ga i sinkroniziraj Firefox među uređajima.]]> Firefox će prestati sinkronizirati s tvojim računom, ali neće izbrisati podatke o tvom pregledavanju na ovom uređaju. @@ -1281,6 +1283,11 @@ The first parameter is the app name --> %s | OSS biblioteke + + Pratitelji preusmjeravanja + + Čisti kolačiće koje postavljaju preusmjeravanja na poznate stranice za praćenje. + Podrška diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 94b397318..c0392a8fc 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -147,6 +147,8 @@ Pasang Tab yang disinkronkan + + Sinkron ulang Temukan di laman @@ -275,6 +277,8 @@ Buka tautan di tab pribadi Izinkan tangkapan layar di penjelajahan pribadi + + Jika diizinkan, tab pribadi juga akan terlihat saat beberapa aplikasi terbuka Tambahkan pintasan penjelajahan pribadi @@ -295,6 +299,8 @@ Tema Beranda + + Gestur Ubahsuai @@ -329,9 +335,13 @@ Cari riwayat jelajah Cari markah + + Cari tab tersinkron Setelan akun + + URL lengkapi-otomatis Buka tautan di aplikasi @@ -474,6 +484,16 @@ Ikuti tema peranti + + + Tarik untuk menyegarkan + + Gulir untuk menyembunyikan bilah alat + + Geser bilah alat ke samping untuk beralih tab + + Geser bilah alat ke atas untuk membuka tab + Sesi @@ -1072,7 +1092,7 @@ - Anda masuk sebagai %s di peramban Firefox lainnya pada ponsel ini. Apakah Anda ingin masuk dengan akun tersebut? + Anda masuk sebagai %s di peramban Firefox lainnya pada perangkat ini. Apakah Anda ingin masuk dengan akun tersebut? Ya, masukkan saya @@ -1161,6 +1181,8 @@ Masuk dengan kamera Anda Gunakan surel saja + + Buat satu untuk menyinkronkan Firefox antar perangkat.]]> Firefox akan berhenti menyinkronkan dengan akun Anda, tetapi tidak akan menghapus data penjelajahan Anda di peranti ini. @@ -1269,6 +1291,11 @@ The first parameter is the app name --> %s | Pustaka OSS + + Alihkan Pelacak + + Menghapus kuki yang ditetapkan oleh pengalihan ke situs web pelacakan yang dikenal. + Dukungan @@ -1315,7 +1342,7 @@ Nama pintasan - Anda dapat dengan mudah menambahkan situs web ini ke layar Beranda ponsel Anda untuk mendapatkan akses instan dan penjelajahan lebih cepat seperti menggunakan aplikasi. + Anda dapat dengan mudah menambahkan situs web ini ke layar Beranda perangkat Anda untuk mendapatkan akses instan dan penjelajahan lebih cepat seperti menggunakan aplikasi. Info masuk dan kata sandi @@ -1385,8 +1412,12 @@ Situs disalin ke clipboard Salin sandi + + Hapus sandi Salin nama pengguna + + Hapus nama pengguna Salin situs @@ -1541,7 +1572,7 @@ Sebuah info masuk dengan nama pengguna tersebut sudah ada - + Hubungkan perangkat lain Harap autentikasi ulang. @@ -1562,7 +1593,7 @@ Batas situs teratas tercapai - Untuk menambahkan situs teratas, hapus salah satu. Tekan lama situs yang dimaksud dan pilih hapus. + Untuk menambahkan situs teratas, hapus salah satu. Tekan lama situs yang dimaksud dan pilih hapus. Oke, Paham! @@ -1572,7 +1603,7 @@ Hapus - Dapatkan hasil maksimal dari %s. @@ -1580,4 +1611,9 @@ Kumpulkan hal-hal yang penting bagi Anda Kelompokkan pencarian, situs, dan tab yang serupa agar dapat diakses cepat nanti. + + Anda masuk sebagai %s di peramban Firefox lainnya pada ponsel ini. Apakah Anda ingin masuk dengan akun tersebut? + + Anda dapat dengan mudah menambahkan situs web ini ke layar Beranda ponsel Anda untuk mendapatkan akses instan dan penjelajahan lebih cepat seperti menggunakan aplikasi. + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 8854fa147..0dde1246c 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1198,6 +1198,8 @@ Accedi con la fotocamera Accedi con l’email + + Creane uno per sincronizzare Firefox tra vari dispositivi.]]> I dati di navigazione non verranno più sincronizzati con l’account Firefox, ma non saranno rimossi da questo dispositivo. @@ -1305,6 +1307,12 @@ The first parameter is the app name --> %s | Librerie OSS + + Traccianti di reindirizzamento + + + Elimina i cookie impostati per reindirizzare a siti web noti per il tracciamento. + Supporto diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index b980d209d..05fe820ab 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -148,6 +148,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Sebded Iccaren yemtawin + + Ales amtawi Nadi deg usebter @@ -270,6 +272,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ldi iseɣwan deg iccer uslig Sireg tuṭṭfa n ugdil deg tunigin tusligt + + Ma yella yettusireg, accaren usligen ad d-banen ula d nutni ma yili aṭas n yisnasen i yeldin Rnu anegzum i tunigin tusligin @@ -324,9 +328,13 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Nadi azray n tunigin Nadi ticraḍ n yisebtar + + Nadi accaren yemtawin Iɣewwaṛen n umiḍan + + Tacaṛt tawurmant n URLs Ldi iseɣwan deg isnasen @@ -1374,6 +1382,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Sfeḍ awal uffir Nɣel isem n useqdac + + Sfeḍ isem n useqdac Nɣel asmel @@ -1552,6 +1562,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Tewwḍeḍ ɣer talast n usmel + + I tmerna n usmel afellay amaynut, kkes yiwen. Nnal ɣef usmel-nni war aḥbas syen fren "kkes". IH, awi-t-id @@ -1561,7 +1573,7 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Kkes - Awi %s amaynut akk. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index dd71adfc3..3676434af 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -144,6 +144,8 @@ Įdiegti Sinchronizuotos kortelės + + Sinchronizuoti iš naujo Rasti tinklalapyje @@ -325,6 +327,8 @@ Ieškoti naršymo žurnale Ieškoti adresyne + + Ieškoti tarp sinchronizuotų kortelių Paskyros nuostatos @@ -1166,6 +1170,8 @@ Prisijunkite su savo kamera Naudoti el. paštą + + Susikurkite, norėdami sinchronizuoti „Firefox“ tarp įrenginių.]]> „Firefox“ nebesinchronizuos duomenų su jūsų paskyra, tačiau šiame įrenginyje esančių naršymo duomenų nepašalins. @@ -1274,6 +1280,11 @@ The first parameter is the app name --> „%s“ | Atvirosios bibliotekos + + Nukreipimų stebėjimo elementai + + Išvalo slapukus, įrašomus nukreipiant į žinomas stebėjimo svetaines. + Žinynas diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 98dca4481..a3d93449d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1175,6 +1175,8 @@ Meld u aan met uw camera E-mail gebruiken + + Maak er een aan om Firefox tussen apparaten te synchroniseren.]]> Firefox stopt de synchronisatie met uw account, maar zal geen surfgegevens op dit apparaat verwijderen. @@ -1282,6 +1284,11 @@ The first parameter is the app name --> %s | OSS-bibliotheken + + Doorleidingstrackers + + Wist cookies die zijn ingesteld door doorgeleidingen naar bekende trackingwebsites. + Ondersteuning diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a1f5c3cea..011f6c979 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1171,6 +1171,8 @@ Zaloguj się za pomocą aparatu Użyj adresu e-mail + + Utwórz je, aby synchronizować Firefoksa między urządzeniami.]]> Firefox zatrzyma synchronizację z tym kontem, ale nie usunie danych przeglądania na tym urządzeniu. @@ -1279,6 +1281,11 @@ The first parameter is the app name --> %s | Biblioteki open source + + Elementy śledzące przez przekierowania + + Czyści ciasteczka ustawione przez przekierowania do znanych witryn śledzących użytkowników. + Pomoc diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 672850019..4876eed68 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -1167,6 +1167,8 @@ Inicie sessão com a sua câmara Como alternativa, utilizar o e-mail + + Crie uma conta para sincronizar o Firefox entre os dispositivos.]]> O Firefox irá parar a sincronização com a sua conta, mas não irá apagar quaisquer dados de navegação seus neste dispositivo. @@ -1277,6 +1279,11 @@ The first parameter is the app name --> %s | Bibliotecas de código aberto + + Rastreadores de redirecionamento + + Limpa as cookies definidas por redirecionamentos para sites de rastreamento conhecidos. + Apoio diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4c5d576fc..e3ee6b96f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -153,6 +153,8 @@ Установить Облачные вкладки + + Повторная синхронизация Найти на странице @@ -1197,6 +1199,8 @@ Войти с помощью камеры Использовать электронную почту + + Создайте его, чтобы синхронизировать Firefox между устройствами.]]> Firefox прекратит синхронизацию с вашим аккаунтом, но не будет удалять ничего из ваших данных веб-сёрфинга на этом устройстве. @@ -1304,6 +1308,11 @@ The first parameter is the app name --> %s | Свободные библиотеки + + Трекеры перенаправлений + + Удаляет куки, установленные в ходе перенаправлений на известные веб-сайты с отслеживанием. + Поддержка diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index 23f0238d5..207213d68 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -143,6 +143,8 @@ Instaloje Skeda të njëkohësuara + + Rinjëkohëso Gjej në faqe @@ -324,6 +326,8 @@ Kërkoni në historik shfletimi Kërkoni te faqerojtësit + + Kërko në skeda të njëkohësuara Rregullime llogarie @@ -1158,6 +1162,8 @@ Hyni me kamerën tuaj Më mirë përdorni email + + Krijoni një të tillë që të njëkohësoni Firefox-in mes pajisjesh.]]> Firefox-i do të reshtë së njëkohësuari me llogarinë tuaj, por s’do të fshijë ndonjë nga të dhënat e shfletimit tuaj në këtë pajisje. @@ -1267,6 +1273,11 @@ The first parameter is the app name --> %s | Biblioteka OSS + + Gjurmues Ridrejtimesh + + Spastron cookies të depozituara nga ridrejtime për te sajte të njohur për gjurmim. + Asistencë diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index dc8783fef..27b449c0d 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -1164,6 +1164,8 @@ Пријава преко камере Користи адресу е-поште + + Направите га за синхронизацију Firefox-а међу уређајима.]]> Firefox ће престати са снихронизацијом вашег налога али неће обрисати ваше податке прегледања на овом уређају. @@ -1277,6 +1279,11 @@ The first parameter is the app name --> %s | библиотеке отвореног кода + + Преусмерите пратиоце + + Брише колачиће постављене преусмеравањем на познате страница за праћење. + Подршка diff --git a/app/src/main/res/values-tg/strings.xml b/app/src/main/res/values-tg/strings.xml index f7b2b6379..ffeb619d1 100644 --- a/app/src/main/res/values-tg/strings.xml +++ b/app/src/main/res/values-tg/strings.xml @@ -832,6 +832,9 @@ Кукиҳо + + Нест кардани маълумоти баррасӣ ҳангоми баромад + Бекор кардан diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 3cb78ea7e..b63bab83c 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -76,6 +76,19 @@ ابھی نہیں + + سیٹنگز پر جائیں + + برخاست کریں + + + سیٹنگز پر جائیں + + برخاست کریں + + + برخاست کریں + نیا ٹیب @@ -472,6 +485,10 @@ بند کریں + + + ٹیب بند کریں + ٹیبس کھولیں @@ -502,9 +519,9 @@ مجموعہ سے ٹیب کو ہٹا دیں - ٹیب بند کر دیں + ٹیب بند کریں - %s ٹیب بند کر دیں + %s ٹیب بند کریں ٹیبز کا مینو کھولیں @@ -594,7 +611,7 @@ تباہی کی رپورٹ Mozilla کو ارسال کریں - ٹیب بند کر دیں + ٹیب بند کریں ٹیب بحال کریں @@ -743,10 +760,6 @@ مجموعہ کا مینو - - آپ کے لئے اہم چیزیں جمع کریں - - ایک طرح کی تلاشیں ، سائٹیں اور ٹیبز کو بعد میں فوری رسائی کے لئے گروہ بندی کریں۔ ٹیب کو منتخب کریں @@ -983,8 +996,8 @@ دیکھیں نیا کیا ہے یہاں جوابات حاصل کریں - - %s کا زیادہ سے زیادہ فائدہ اٹھائیں۔ + + مزید سیکھیں ہاں ، مجھے سائن ان کریں @@ -1427,7 +1440,7 @@ اس صارف نام کے ساتھ لاگ ان پہلے سے موجود ہے - + ایک اور آلہ جوڑیں۔ برائے مہربانی دوبارہ توثیق کریں۔ @@ -1446,9 +1459,18 @@ اعلٰی سائٹ کی حد پوری ہو گئی - - نئی ٹاپ سائٹ شامل کرنے کے لئے ، ایک سائٹ کو ہٹائیں۔ سائٹ پر دیر تک دبائیں اور ہٹانا منتخب کریں۔ ٹھیک ہے سمجھ گیا - + + ہٹائیں + + + %s کا زیادہ سے زیادہ فائدہ اٹھائیں۔ + + + آپ کے لئے اہم چیزیں جمع کریں + + ایک طرح کی تلاشیں ، سائٹیں اور ٹیبز کو بعد میں فوری رسائی کے لئے گروہ بندی کریں۔ + From 3983c509dc248151fc263fa61ece2e8c921c62c7 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 21 Sep 2020 16:24:56 +0200 Subject: [PATCH 05/30] Use "undo" implementation from Android Components. This is not the super fancy version yet - since we still need to restore into SessionManager and haven't fully switched to BrowserStore yet. However AC having knowledge about "undo" and whether it was performed or not, will help us with features like "recently closed tabs". And once we can improve "undo", Fenix will get all the nice things automatically. Requires: https://github.com/mozilla-mobile/android-components/pull/8449 --- .../fenix/browser/BaseBrowserFragment.kt | 10 +-- .../java/org/mozilla/fenix/components/Core.kt | 9 ++- .../org/mozilla/fenix/home/HomeFragment.kt | 67 +++++++------------ .../fenix/tabtray/TabTrayDialogFragment.kt | 13 +--- .../main/java/org/mozilla/fenix/utils/Undo.kt | 21 ++++-- 5 files changed, 51 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 72c2cd9d5..352fcc522 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -260,9 +260,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session ) }, onCloseTab = { closedSession -> - val tab = store.state.findTab(closedSession.id) - ?: return@DefaultBrowserToolbarController - val isSelected = tab.id == context.components.core.store.state.selectedTabId + val tab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController val snackbarMessage = if (tab.content.private) { requireContext().getString(R.string.snackbar_private_tab_closed) @@ -275,11 +273,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session snackbarMessage, requireContext().getString(R.string.snackbar_deleted_undo), { - sessionManager.add( - closedSession, - isSelected, - engineSessionState = tab.engineState.engineSessionState - ) + requireComponents.useCases.tabsUseCases.undo.invoke() }, operation = { } ) diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index fbec09a5d..a2ae6f888 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -20,6 +20,7 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.engine.EngineMiddleware import mozilla.components.browser.session.storage.SessionStorage +import mozilla.components.browser.session.undo.UndoMiddleware import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore @@ -69,6 +70,7 @@ import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.advanced.getSelectedLocale import org.mozilla.fenix.utils.Mockable +import org.mozilla.fenix.utils.getUndoDelay import java.util.concurrent.TimeUnit /** @@ -146,13 +148,18 @@ class Core(private val context: Context, private val crashReporter: CrashReporti MediaMiddleware(context, MediaService::class.java), DownloadMiddleware(context, DownloadService::class.java), ReaderViewMiddleware(), - ThumbnailsMiddleware(thumbnailStorage) + ThumbnailsMiddleware(thumbnailStorage), + UndoMiddleware(::lookupSessionManager, context.getUndoDelay()) ) + EngineMiddleware.create(engine, ::findSessionById) ).also { it.dispatch(RecentlyClosedAction.InitializeRecentlyClosedState) } } + private fun lookupSessionManager(): SessionManager { + return sessionManager + } + private fun findSessionById(tabId: String): Session? { return sessionManager.findSessionById(tabId) } diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 6df4a4131..707c584ee 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -92,7 +92,6 @@ import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.resetPoliciesAfter -import org.mozilla.fenix.ext.sessionsOfType import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor @@ -469,17 +468,10 @@ class HomeFragment : Fragment() { } private fun removeAllTabsAndShowSnackbar(sessionCode: String) { - val tabs = sessionManager.sessionsOfType(private = sessionCode == ALL_PRIVATE_TABS).toList() - val selectedIndex = sessionManager - .selectedSession?.let { sessionManager.sessions.indexOf(it) } - ?: SessionManager.NO_SELECTION - - val snapshot = tabs - .map(sessionManager::createSessionSnapshot) - .let { SessionManager.Snapshot(it, selectedIndex) } - - tabs.forEach { - requireComponents.useCases.tabsUseCases.removeTab(it) + if (sessionCode == ALL_PRIVATE_TABS) { + sessionManager.removePrivateSessions() + } else { + sessionManager.removeNormalSessions() } val snackbarMessage = if (sessionCode == ALL_PRIVATE_TABS) { @@ -493,7 +485,7 @@ class HomeFragment : Fragment() { snackbarMessage, requireContext().getString(R.string.snackbar_deleted_undo), { - sessionManager.restore(snapshot) + requireComponents.useCases.tabsUseCases.undo.invoke() }, operation = { }, anchorView = snackbarAnchorView @@ -501,38 +493,29 @@ class HomeFragment : Fragment() { } private fun removeTabAndShowSnackbar(sessionId: String) { - sessionManager.findSessionById(sessionId)?.let { session -> - val snapshot = sessionManager.createSessionSnapshot(session) - val state = store.state.findTab(sessionId)?.engineState?.engineSessionState - val isSelected = - session.id == requireComponents.core.store.state.selectedTabId ?: false + val tab = store.state.findTab(sessionId) ?: return - requireComponents.useCases.tabsUseCases.removeTab(sessionId) - - val snackbarMessage = if (snapshot.session.private) { - requireContext().getString(R.string.snackbar_private_tab_closed) - } else { - requireContext().getString(R.string.snackbar_tab_closed) - } + requireComponents.useCases.tabsUseCases.removeTab(sessionId) - viewLifecycleOwner.lifecycleScope.allowUndo( - requireView(), - snackbarMessage, - requireContext().getString(R.string.snackbar_deleted_undo), - { - sessionManager.add( - snapshot.session, - isSelected, - engineSessionState = state - ) - findNavController().navigate( - HomeFragmentDirections.actionGlobalBrowser(null) - ) - }, - operation = { }, - anchorView = snackbarAnchorView - ) + val snackbarMessage = if (tab.content.private) { + requireContext().getString(R.string.snackbar_private_tab_closed) + } else { + requireContext().getString(R.string.snackbar_tab_closed) } + + viewLifecycleOwner.lifecycleScope.allowUndo( + requireView(), + snackbarMessage, + requireContext().getString(R.string.snackbar_deleted_undo), + { + requireComponents.useCases.tabsUseCases.undo.invoke() + findNavController().navigate( + HomeFragmentDirections.actionGlobalBrowser(null) + ) + }, + operation = { }, + anchorView = snackbarAnchorView + ) } override fun onDestroyView() { diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt index f03bd170c..858be4e76 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -260,10 +260,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler private fun showUndoSnackbarForTab(sessionId: String) { val store = requireComponents.core.store - val sessionManager = requireComponents.core.sessionManager - val tab = requireComponents.core.store.state.findTab(sessionId) ?: return - val session = sessionManager.findSessionById(sessionId) ?: return // Check if this is the last tab of this session type val isLastOpenTab = @@ -273,8 +270,6 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler return } - val isSelected = sessionId == requireComponents.core.store.state.selectedTabId ?: false - val snackbarMessage = if (tab.content.private) { getString(R.string.snackbar_private_tab_closed) } else { @@ -286,12 +281,8 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler snackbarMessage, getString(R.string.snackbar_deleted_undo), { - sessionManager.add( - session, - isSelected, - engineSessionState = tab.engineState.engineSessionState - ) - _tabTrayView?.scrollToTab(session.id) + requireComponents.useCases.tabsUseCases.undo.invoke() + _tabTrayView?.scrollToTab(tab.id) }, operation = { }, elevation = ELEVATION, diff --git a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt index 26dbf59ee..6dd272f5a 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.utils +import android.content.Context import android.view.View import androidx.appcompat.widget.ContentFrameLayout import androidx.core.view.updatePadding @@ -19,6 +20,18 @@ import java.util.concurrent.atomic.AtomicBoolean internal const val UNDO_DELAY = 3000L internal const val ACCESSIBLE_UNDO_DELAY = 15000L +/** + * Get the recommended time an "undo" action should be available until it can automatically be + * dismissed. The delay may be different based on the accessibility settings of the device. + */ +fun Context.getUndoDelay(): Long { + return if (settings().accessibilityServicesEnabled) { + ACCESSIBLE_UNDO_DELAY + } else { + UNDO_DELAY + } +} + /** * Runs [operation] after giving user time (see [UNDO_DELAY]) to cancel it. * In case of cancellation, [onCancel] is executed. @@ -92,13 +105,7 @@ fun CoroutineScope.allowUndo( // Wait a bit, and if user didn't request cancellation, proceed with // requested operation and hide the snackbar. launch { - val lengthToDelay = if (view.context.settings().accessibilityServicesEnabled) { - ACCESSIBLE_UNDO_DELAY - } else { - UNDO_DELAY - } - - delay(lengthToDelay) + delay(view.context.getUndoDelay()) if (!requestedUndo.get()) { snackbar.dismiss() From f05a5420600d0fe7bf7b9c220f0181e6cd82e3e8 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 22 Sep 2020 14:50:25 -0700 Subject: [PATCH 06/30] For #13959: always enable detectNonSdkApiUsage. We don't use penalty death for the VM policy so we theoretically don't need to disable this check if penalty death is enabled. --- app/src/main/java/org/mozilla/fenix/StrictModeManager.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt index b624b097f..a9e6947ff 100644 --- a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt +++ b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt @@ -47,11 +47,7 @@ object StrictModeManager { builder.detectContentUriWithoutPermission() } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - if (setPenaltyDeath || setPenaltyDialog) { - builder.permitNonSdkApiUsage() - } else { - builder.detectNonSdkApiUsage() - } + builder.detectNonSdkApiUsage() } StrictMode.setVmPolicy(builder.build()) } From c03c7ef79381c527e7045fafdb2ee8a9eb1e5953 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 22 Sep 2020 14:52:31 -0700 Subject: [PATCH 07/30] For #13959: remove unused penaltyDialog parameter. Additional branching introduces complexity so we should avoid it when possible. This branch was also unused so it's more likely to have bugs if we tried to use it after some refactor. --- .../main/java/org/mozilla/fenix/StrictModeManager.kt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt index a9e6947ff..e0dd864ad 100644 --- a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt +++ b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt @@ -17,10 +17,8 @@ object StrictModeManager { /*** * Enables strict mode for debug purposes. meant to be run only in the main process. * @param setPenaltyDeath boolean value to decide setting the penaltyDeath as a penalty. - * @param setPenaltyDialog boolean value to decide setting the dialog box as a penalty. - * Note: dialog penalty cannot be set with penaltyDeath */ - fun enableStrictMode(setPenaltyDeath: Boolean, setPenaltyDialog: Boolean = false) { + fun enableStrictMode(setPenaltyDeath: Boolean) { if (Config.channel.isDebug) { val threadPolicy = StrictMode.ThreadPolicy.Builder() .detectAll() @@ -28,12 +26,6 @@ object StrictModeManager { if (setPenaltyDeath && Build.MANUFACTURER !in strictModeExceptionList) { threadPolicy.penaltyDeath() } - - // dialog penalty cannot be set with penaltyDeath - if (!setPenaltyDeath && setPenaltyDialog) { - threadPolicy.penaltyDialog() - } - StrictMode.setThreadPolicy(threadPolicy.build()) val builder = StrictMode.VmPolicy.Builder() @@ -62,7 +54,7 @@ object StrictModeManager { fragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { - enableStrictMode(setPenaltyDeath = false, setPenaltyDialog = false) + enableStrictMode(setPenaltyDeath = false) fm.unregisterFragmentLifecycleCallbacks(this) } }, false) From 2c1befaa2552012f11766c80be902eac1a1a6c31 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 22 Sep 2020 14:56:25 -0700 Subject: [PATCH 08/30] For #13959: rename to attachListenerToDisablePenaltyDeath for clarity. --- app/src/main/java/org/mozilla/fenix/HomeActivity.kt | 2 +- app/src/main/java/org/mozilla/fenix/StrictModeManager.kt | 4 ++-- app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index b1109f73f..ea8d863de 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -150,7 +150,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { private lateinit var navigationToolbar: Toolbar final override fun onCreate(savedInstanceState: Bundle?) { - StrictModeManager.changeStrictModePolicies(supportFragmentManager) + StrictModeManager.attachListenerToDisablePenaltyDeath(supportFragmentManager) // There is disk read violations on some devices such as samsung and pixel for android 9/10 StrictMode.allowThreadDiskReads().resetPoliciesAfter { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt index e0dd864ad..a64e1598e 100644 --- a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt +++ b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt @@ -46,11 +46,11 @@ object StrictModeManager { } /** - * Revert strict mode to disable penalty. Tied to fragment lifecycle since strict mode + * Revert strict mode to disable penalty based on fragment lifecycle since strict mode * needs to switch to penalty logs. Using the fragment life cycle allows decoupling from any * specific fragment. */ - fun changeStrictModePolicies(fragmentManager: FragmentManager) { + fun attachListenerToDisablePenaltyDeath(fragmentManager: FragmentManager) { fragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { diff --git a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt index 560787384..62ec64d28 100644 --- a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt @@ -63,7 +63,7 @@ class StrictModeManagerTest { fun `test changeStrictModePolicies`() { val callbacks = slot() - StrictModeManager.changeStrictModePolicies(fragmentManager) + StrictModeManager.attachListenerToDisablePenaltyDeath(fragmentManager) verify { fragmentManager.registerFragmentLifecycleCallbacks(capture(callbacks), false) } confirmVerified(fragmentManager) From 3bf71ef655fdb5e0e93aabb4834b06fded37a32b Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 22 Sep 2020 15:04:06 -0700 Subject: [PATCH 09/30] For #13959: use ac StrictMode.resetAfter rather than duplicating functionality. --- app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt b/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt index ecb73dc39..3caf21b98 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.ext import android.os.StrictMode +import mozilla.components.support.ktx.android.os.resetAfter import org.mozilla.fenix.Config /** @@ -15,11 +16,7 @@ import org.mozilla.fenix.Config */ inline fun StrictMode.ThreadPolicy.resetPoliciesAfter(functionBlock: () -> R): R { return if (Config.channel.isDebug) { - try { - functionBlock() - } finally { - StrictMode.setThreadPolicy(this) - } + resetAfter { functionBlock() } } else { functionBlock() } From e1bd6191c7a37cbbb0fecdbf4c9b3743fa74b075 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 22 Sep 2020 15:12:25 -0700 Subject: [PATCH 10/30] For #13959: add comment to explain why we only resetAfter in certain build modes. --- app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt b/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt index 3caf21b98..634482c31 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt @@ -15,6 +15,8 @@ import org.mozilla.fenix.Config * @return the value returned by [functionBlock]. */ inline fun StrictMode.ThreadPolicy.resetPoliciesAfter(functionBlock: () -> R): R { + // Calling resetAfter takes 1-2ms (unknown device) so we only execute it if StrictMode can + // actually be enabled. https://github.com/mozilla-mobile/fenix/issues/11617 return if (Config.channel.isDebug) { resetAfter { functionBlock() } } else { From a92356fe00c4f89f755e8d53231a7cf8ccb11b3d Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Thu, 24 Sep 2020 14:20:41 -0700 Subject: [PATCH 11/30] For #13959: comment about duplication in logic in StrictMode. I had to drop a commit that addressed the issue because it was too hard to fix. --- app/src/main/java/org/mozilla/fenix/StrictModeManager.kt | 7 +++++++ app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt | 3 +++ 2 files changed, 10 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt index a64e1598e..25944f7d6 100644 --- a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt +++ b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt @@ -11,6 +11,10 @@ import androidx.fragment.app.FragmentManager /** * Manages strict mode settings for the application. + * + * Due to the static dependencies in this class, it's getting hard to test: if this class takes any + * many more static dependencies, consider switching it, and other code that uses [StrictMode] like + * [StrictMode.ThreadPolicy].resetPoliciesAfter, to a class with dependency injection. */ object StrictModeManager { @@ -19,6 +23,9 @@ object StrictModeManager { * @param setPenaltyDeath boolean value to decide setting the penaltyDeath as a penalty. */ fun enableStrictMode(setPenaltyDeath: Boolean) { + // The expression in this if is duplicated in StrictMode.ThreadPolicy.resetPoliciesAfter + // because the tests break in unexpected ways if the value is shared as a constant in this + // class. It wasn't worth the time to address it. if (Config.channel.isDebug) { val threadPolicy = StrictMode.ThreadPolicy.Builder() .detectAll() diff --git a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt b/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt index 634482c31..54985108f 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt @@ -17,6 +17,9 @@ import org.mozilla.fenix.Config inline fun StrictMode.ThreadPolicy.resetPoliciesAfter(functionBlock: () -> R): R { // Calling resetAfter takes 1-2ms (unknown device) so we only execute it if StrictMode can // actually be enabled. https://github.com/mozilla-mobile/fenix/issues/11617 + // + // The expression in this if is duplicated in StrictModeManager.enableStrictMode: see that method + // for details. return if (Config.channel.isDebug) { resetAfter { functionBlock() } } else { From d4ab728cff0991645306205cc5171d8a1993250f Mon Sep 17 00:00:00 2001 From: Christian Sadilek Date: Thu, 24 Sep 2020 17:44:22 -0400 Subject: [PATCH 12/30] For #14034: Add debug preference to override AMO collection in Nightly --- .../mozilla/fenix/components/Components.kt | 18 ++++- .../fenix/settings/SettingsFragment.kt | 63 ++++++++++++++- .../java/org/mozilla/fenix/utils/Settings.kt | 14 ++++ .../layout/amo_collection_override_dialog.xml | 32 ++++++++ app/src/main/res/values/preference_keys.xml | 2 + app/src/main/res/values/strings.xml | 14 ++++ app/src/main/res/xml/preferences.xml | 5 ++ .../fenix/settings/SettingsFragmentTest.kt | 76 +++++++++++++++++++ .../org/mozilla/fenix/utils/SettingsTest.kt | 30 ++++++++ 9 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/layout/amo_collection_override_dialog.xml create mode 100644 app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index f6e94701b..24846cc4d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -17,8 +17,10 @@ import mozilla.components.feature.addons.update.DefaultAddonUpdater import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.migration.state.MigrationStore import org.mozilla.fenix.BuildConfig +import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.components.metrics.AppStartupTelemetry +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.utils.ClipboardHandler import org.mozilla.fenix.utils.Mockable import org.mozilla.fenix.utils.Settings @@ -71,14 +73,26 @@ class Components(private val context: Context) { } val addonCollectionProvider by lazy { - if (!BuildConfig.AMO_COLLECTION.isNullOrEmpty()) { + // Check if we have a customized (overridden) AMO collection (only supported in Nightly) + if (Config.channel.isNightlyOrDebug && context.settings().amoCollectionOverrideConfigured()) { + AddonCollectionProvider( + context, + core.client, + collectionUser = context.settings().overrideAmoUser, + collectionName = context.settings().overrideAmoCollection + ) + } + // Use build config otherwise + else if (!BuildConfig.AMO_COLLECTION.isNullOrEmpty()) { AddonCollectionProvider( context, core.client, collectionName = BuildConfig.AMO_COLLECTION, maxCacheAgeInMinutes = DAY_IN_MINUTES ) - } else { + } + // Fall back to defaults + else { AddonCollectionProvider(context, core.client, maxCacheAgeInMinutes = DAY_IN_MINUTES) } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 6f0719487..df29c2916 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -6,13 +6,16 @@ package org.mozilla.fenix.settings import android.annotation.SuppressLint import android.content.ActivityNotFoundException +import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Handler -import android.provider.Settings +import android.view.LayoutInflater import android.widget.Toast +import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDirections import androidx.navigation.findNavController @@ -20,6 +23,8 @@ import androidx.navigation.fragment.navArgs import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.amo_collection_override_dialog.view.* +import kotlinx.android.synthetic.main.fragment_installed_add_on_details.view.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -28,6 +33,7 @@ import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.Profile import mozilla.components.support.ktx.android.content.getColorFromAttr +import mozilla.components.support.ktx.android.view.showKeyboard import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.Config import org.mozilla.fenix.FeatureFlags @@ -43,6 +49,7 @@ import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.account.AccountUiView +import org.mozilla.fenix.utils.Settings import kotlin.system.exitProcess @Suppress("LargeClass", "TooManyFunctions") @@ -304,6 +311,41 @@ class SettingsFragment : PreferenceFragmentCompat() { resources.getString(R.string.pref_key_debug_settings) -> { SettingsFragmentDirections.actionSettingsFragmentToSecretSettingsFragment() } + resources.getString(R.string.pref_key_override_amo_collection) -> { + val context = requireContext() + val dialogView = LayoutInflater.from(context).inflate(R.layout.amo_collection_override_dialog, null) + + AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.preferences_customize_amo_collection)) + setView(dialogView) + setNegativeButton(R.string.customize_addon_collection_cancel) { dialog: DialogInterface, _ -> + dialog.cancel() + } + + setPositiveButton(R.string.customize_addon_collection_ok) { _, _ -> + context.settings().overrideAmoUser = dialogView.custom_amo_user.text.toString() + context.settings().overrideAmoCollection = dialogView.custom_amo_collection.text.toString() + + Toast.makeText( + context, + getString(R.string.toast_customize_addon_collection_done), + Toast.LENGTH_LONG + ).show() + + Handler().postDelayed({ + exitProcess(0) + }, AMO_COLLECTION_OVERRIDE_EXIT_DELAY) + } + + dialogView.custom_amo_collection.setText(context.settings().overrideAmoCollection) + dialogView.custom_amo_user.setText(context.settings().overrideAmoUser) + dialogView.custom_amo_user.requestFocus() + dialogView.custom_amo_user.showKeyboard() + create() + }.show() + + null + } else -> null } directions?.let { navigateFromSettings(directions) } @@ -374,12 +416,14 @@ class SettingsFragment : PreferenceFragmentCompat() { findPreference( getPreferenceKey(R.string.pref_key_debug_settings) )?.isVisible = requireContext().settings().showSecretDebugMenuThisSession + + setupAmoCollectionOverridePreference(requireContext().settings()) } private fun getClickListenerForMakeDefaultBrowser(): Preference.OnPreferenceClickListener { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Preference.OnPreferenceClickListener { - val intent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + val intent = Intent(android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) startActivity(intent) true } @@ -446,8 +490,23 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + @VisibleForTesting + internal fun setupAmoCollectionOverridePreference(settings: Settings) { + val preferenceAmoCollectionOverride = + findPreference(getPreferenceKey(R.string.pref_key_override_amo_collection)) + + val show = (Config.channel.isNightlyOrDebug && ( + settings.amoCollectionOverrideConfigured() || settings.showSecretDebugMenuThisSession) + ) + preferenceAmoCollectionOverride?.apply { + isVisible = show + summary = settings.overrideAmoCollection.ifEmpty { null } + } + } + companion object { private const val SCROLL_INDICATOR_DELAY = 10L private const val FXA_SYNC_OVERRIDE_EXIT_DELAY = 2000L + private const val AMO_COLLECTION_OVERRIDE_EXIT_DELAY = 3000L } } diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index a0bd0ad76..16f07e438 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -886,6 +886,20 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = "" ) + var overrideAmoUser by stringPreference( + appContext.getPreferenceKey(R.string.pref_key_override_amo_user), + default = "" + ) + + var overrideAmoCollection by stringPreference( + appContext.getPreferenceKey(R.string.pref_key_override_amo_collection), + default = "" + ) + + fun amoCollectionOverrideConfigured(): Boolean { + return overrideAmoUser.isNotEmpty() || overrideAmoCollection.isNotEmpty() + } + val topSitesSize by intPreference( appContext.getPreferenceKey(R.string.pref_key_top_sites_size), default = 0 diff --git a/app/src/main/res/layout/amo_collection_override_dialog.xml b/app/src/main/res/layout/amo_collection_override_dialog.xml new file mode 100644 index 000000000..b913795b4 --- /dev/null +++ b/app/src/main/res/layout/amo_collection_override_dialog.xml @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 63673a9b5..d455228fe 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -35,6 +35,8 @@ pref_key_delete_browsing_data_on_quit_categories pref_key_last_known_mode_private pref_key_addons + pref_key_override_amo_user + pref_key_override_amo_collection pref_key_last_maintenance pref_key_help pref_key_rate diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4a9e2c55b..1465e3623 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -337,6 +337,20 @@ Notifications + + + Custom Add-on collection + + OK + + Cancel + + Collection name + + Collection owner (User ID) + + Add-on collection modified. Quitting the application to apply changes… + Sync now diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index e1f18cd8c..9fcdcf316 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -136,6 +136,11 @@ android:key="@string/pref_key_addons" android:title="@string/preferences_addons" /> + + ( + settingsFragment.getPreferenceKey(R.string.pref_key_override_amo_collection) + ) + + settingsFragment.setupAmoCollectionOverridePreference(mockk(relaxed = true)) + assertNotNull(preferenceAmoCollectionOverride) + assertFalse(preferenceAmoCollectionOverride!!.isVisible) + + val settings: Settings = mockk(relaxed = true) + every { settings.showSecretDebugMenuThisSession } returns true + settingsFragment.setupAmoCollectionOverridePreference(settings) + assertTrue(preferenceAmoCollectionOverride.isVisible) + } + + @Test + fun `Add-on collection override pref is visible if already configured`() { + val settingsFragment = SettingsFragment() + val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().get() + + activity.supportFragmentManager.beginTransaction() + .add(settingsFragment, "test") + .commitNow() + + val preferenceAmoCollectionOverride = settingsFragment.findPreference( + settingsFragment.getPreferenceKey(R.string.pref_key_override_amo_collection) + ) + + settingsFragment.setupAmoCollectionOverridePreference(mockk(relaxed = true)) + assertNotNull(preferenceAmoCollectionOverride) + assertFalse(preferenceAmoCollectionOverride!!.isVisible) + + val settings: Settings = mockk(relaxed = true) + every { settings.showSecretDebugMenuThisSession } returns false + + every { settings.amoCollectionOverrideConfigured() } returns false + settingsFragment.setupAmoCollectionOverridePreference(settings) + assertFalse(preferenceAmoCollectionOverride.isVisible) + + every { settings.amoCollectionOverrideConfigured() } returns true + settingsFragment.setupAmoCollectionOverridePreference(settings) + assertTrue(preferenceAmoCollectionOverride.isVisible) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt index 456b946e8..4ff101244 100644 --- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -576,4 +576,34 @@ class SettingsTest { settings.getSitePermissionsCustomSettingsRules() ) } + + @Test + fun overrideAmoCollection() { + // When just created + // Then + assertEquals("", settings.overrideAmoCollection) + assertFalse(settings.amoCollectionOverrideConfigured()) + + // When + settings.overrideAmoCollection = "testCollection" + + // Then + assertEquals("testCollection", settings.overrideAmoCollection) + assertTrue(settings.amoCollectionOverrideConfigured()) + } + + @Test + fun overrideAmoUser() { + // When just created + // Then + assertEquals("", settings.overrideAmoUser) + assertFalse(settings.amoCollectionOverrideConfigured()) + + // When + settings.overrideAmoUser = "testAmoUser" + + // Then + assertEquals("testAmoUser", settings.overrideAmoUser) + assertTrue(settings.amoCollectionOverrideConfigured()) + } } From 6abeb2d9e78df4e927ba58b150cd511a7e77c9d4 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Thu, 24 Sep 2020 15:21:15 -0700 Subject: [PATCH 13/30] For #13959: change StrictModeManager to class from object. I originally tried to create this PR leaving this as an object to keep the change simple but it wasn't worth it - once the object started to keep state, we'd need to manually reset the state between runs. Also, the tests were already getting hacky with static mocking so it was easier to address some of those issues this way too. --- .../org/mozilla/fenix/FenixApplication.kt | 3 +-- .../java/org/mozilla/fenix/HomeActivity.kt | 2 +- .../org/mozilla/fenix/StrictModeManager.kt | 21 +++++++-------- .../mozilla/fenix/components/Components.kt | 2 ++ .../mozilla/fenix/StrictModeManagerTest.kt | 27 ++++++++++--------- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index e3b7e9ed6..8213dcb03 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -40,7 +40,6 @@ import mozilla.components.support.rusthttp.RustHttpConfig import mozilla.components.support.rustlog.RustLog import mozilla.components.support.utils.logElapsedTime import mozilla.components.support.webextensions.WebExtensionSupport -import org.mozilla.fenix.StrictModeManager.enableStrictMode import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.ext.components @@ -126,7 +125,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { val megazordSetup = setupMegazord() setDayNightTheme() - enableStrictMode(true) + components.strictMode.enableStrictMode(true) warmBrowsersCache() // Make sure the engine is initialized and ready to use. diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index ea8d863de..e997c9f55 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -150,7 +150,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { private lateinit var navigationToolbar: Toolbar final override fun onCreate(savedInstanceState: Bundle?) { - StrictModeManager.attachListenerToDisablePenaltyDeath(supportFragmentManager) + components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager) // There is disk read violations on some devices such as samsung and pixel for android 9/10 StrictMode.allowThreadDiskReads().resetPoliciesAfter { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt index 25944f7d6..2b9594e50 100644 --- a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt +++ b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt @@ -9,24 +9,24 @@ import android.os.StrictMode import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +private const val MANUFACTURE_HUAWEI: String = "HUAWEI" +private const val MANUFACTURE_ONE_PLUS: String = "OnePlus" + /** * Manages strict mode settings for the application. - * - * Due to the static dependencies in this class, it's getting hard to test: if this class takes any - * many more static dependencies, consider switching it, and other code that uses [StrictMode] like - * [StrictMode.ThreadPolicy].resetPoliciesAfter, to a class with dependency injection. */ -object StrictModeManager { +class StrictModeManager(config: Config) { + + // The expression in this if is duplicated in StrictMode.ThreadPolicy.resetPoliciesAfter + // because we don't want to have to pass in a dependency each time the ext fn is called. + private val isEnabledByBuildConfig = config.channel.isDebug /*** * Enables strict mode for debug purposes. meant to be run only in the main process. * @param setPenaltyDeath boolean value to decide setting the penaltyDeath as a penalty. */ fun enableStrictMode(setPenaltyDeath: Boolean) { - // The expression in this if is duplicated in StrictMode.ThreadPolicy.resetPoliciesAfter - // because the tests break in unexpected ways if the value is shared as a constant in this - // class. It wasn't worth the time to address it. - if (Config.channel.isDebug) { + if (isEnabledByBuildConfig) { val threadPolicy = StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() @@ -67,9 +67,6 @@ object StrictModeManager { }, false) } - private const val MANUFACTURE_HUAWEI: String = "HUAWEI" - private const val MANUFACTURE_ONE_PLUS: String = "OnePlus" - /** * There are certain manufacturers that have custom font classes for the OS systems. * These classes violates the [StrictMode] policies on startup. As a workaround, we create diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 24846cc4d..8bcbce420 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -19,6 +19,7 @@ import mozilla.components.support.migration.state.MigrationStore import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.components.metrics.AppStartupTelemetry import org.mozilla.fenix.ext.settings import org.mozilla.fenix.utils.ClipboardHandler @@ -126,6 +127,7 @@ class Components(private val context: Context) { val performance by lazy { PerformanceComponent() } val push by lazy { Push(context, analytics.crashReporter) } val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context as Application) } + val strictMode by lazy { StrictModeManager(Config) } val settings by lazy { Settings(context) } diff --git a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt index 62ec64d28..5c5601c9d 100644 --- a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt @@ -11,10 +11,8 @@ import io.mockk.confirmVerified import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk -import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.slot -import io.mockk.unmockkObject import io.mockk.unmockkStatic import io.mockk.verify import org.junit.After @@ -26,44 +24,49 @@ import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @RunWith(FenixRobolectricTestRunner::class) class StrictModeManagerTest { + private lateinit var debugManager: StrictModeManager + private lateinit var releaseManager: StrictModeManager + @MockK(relaxUnitFun = true) private lateinit var fragmentManager: FragmentManager @Before fun setup() { MockKAnnotations.init(this) mockkStatic(StrictMode::class) - mockkObject(Config) + + // These tests log a warning that mockk couldn't set the backing field of Config.channel + // but it doesn't seem to impact their correctness so I'm ignoring it. + val debugConfig: Config = mockk { every { channel } returns ReleaseChannel.Debug } + debugManager = StrictModeManager(debugConfig) + + val releaseConfig: Config = mockk { every { channel } returns ReleaseChannel.Release } + releaseManager = StrictModeManager(releaseConfig) } @After fun teardown() { unmockkStatic(StrictMode::class) - unmockkObject(Config) } @Test fun `test enableStrictMode in release`() { - every { Config.channel } returns ReleaseChannel.Release - StrictModeManager.enableStrictMode(false) - + releaseManager.enableStrictMode(false) verify(exactly = 0) { StrictMode.setThreadPolicy(any()) } verify(exactly = 0) { StrictMode.setVmPolicy(any()) } } @Test fun `test enableStrictMode in debug`() { - every { Config.channel } returns ReleaseChannel.Debug - StrictModeManager.enableStrictMode(false) - + debugManager.enableStrictMode(false) verify { StrictMode.setThreadPolicy(any()) } verify { StrictMode.setVmPolicy(any()) } } @Test - fun `test changeStrictModePolicies`() { + fun `test changeStrictModePolicies in debug`() { val callbacks = slot() - StrictModeManager.attachListenerToDisablePenaltyDeath(fragmentManager) + debugManager.attachListenerToDisablePenaltyDeath(fragmentManager) verify { fragmentManager.registerFragmentLifecycleCallbacks(capture(callbacks), false) } confirmVerified(fragmentManager) From f19c9920f9f279032437b3cb251c0af5eff19d0f Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Thu, 24 Sep 2020 16:35:36 -0700 Subject: [PATCH 14/30] For #13959: move resetAfter into StrictModeManager. In a followup PR, we need to add state to strictModeManager (the number of suppressions). This is much simpler to do when this is defined as a class rather than an object. However, when this is defined as a class, `resetAfter` needs access to the strictModeManager. Instead of passing it in as an argument, it made sense to move this function onto the strictModeManager instead. Since folks are used to calling: ``` StrictMode.ThreadPolicy.allowThreadDiskReads().resetAfter ``` We're going to have to add a lint check to prevent them from doing that. --- .../mozilla/fenix/DebugFenixApplication.kt | 3 +- .../org/mozilla/fenix/FenixApplication.kt | 5 ++-- .../java/org/mozilla/fenix/HomeActivity.kt | 5 ++-- .../mozilla/fenix/IntentReceiverActivity.kt | 5 ++-- .../org/mozilla/fenix/StrictModeManager.kt | 28 +++++++++++++++++-- .../mozilla/fenix/browser/BrowserFragment.kt | 3 +- .../fenix/components/AccountAbnormalities.kt | 7 +++-- .../org/mozilla/fenix/components/Analytics.kt | 9 ++++-- .../fenix/components/BackgroundServices.kt | 6 ++-- .../mozilla/fenix/components/Components.kt | 7 +++-- .../java/org/mozilla/fenix/components/Core.kt | 16 ++++++++--- .../fenix/components/TabCollectionStorage.kt | 5 ++-- .../metrics/LeanplumMetricsService.kt | 5 ++-- .../org/mozilla/fenix/home/HomeFragment.kt | 7 ++--- .../org/mozilla/fenix/whatsnew/WhatsNew.kt | 4 +-- .../fenix/IntentReceiverActivityTest.kt | 1 + .../components/AccountAbnormalitiesTest.kt | 14 +++++----- .../fenix/components/TestComponents.kt | 2 +- .../org/mozilla/fenix/components/TestCore.kt | 6 +++- .../metrics/LeanplumMetricsServiceTest.kt | 13 +++++++-- 20 files changed, 100 insertions(+), 51 deletions(-) diff --git a/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt b/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt index 7f5c86762..7825870b0 100644 --- a/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt +++ b/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt @@ -9,12 +9,11 @@ import androidx.preference.PreferenceManager import leakcanary.AppWatcher import leakcanary.LeakCanary import org.mozilla.fenix.ext.getPreferenceKey -import org.mozilla.fenix.ext.resetPoliciesAfter class DebugFenixApplication : FenixApplication() { override fun setupLeakCanary() { - val isEnabled = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + val isEnabled = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { PreferenceManager.getDefaultSharedPreferences(this) .getBoolean(getPreferenceKey(R.string.pref_key_leakcanary), true) } diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 8213dcb03..750a68d4c 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -43,7 +43,6 @@ import mozilla.components.support.webextensions.WebExtensionSupport import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.perf.StorageStatsMetrics import org.mozilla.fenix.perf.StartupTimeline @@ -129,7 +128,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { warmBrowsersCache() // Make sure the engine is initialized and ready to use. - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { components.core.engine.warmUp() } initializeWebExtensionSupport() @@ -435,7 +434,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { applicationContext.resources.configuration.uiMode = config.uiMode // random StrictMode onDiskRead violation even when Fenix is not running in the background. - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.onConfigurationChanged(config) } } diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index e997c9f55..b0615d7da 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -73,7 +73,6 @@ import org.mozilla.fenix.ext.breadcrumb import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor @@ -152,7 +151,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { final override fun onCreate(savedInstanceState: Bundle?) { components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager) // There is disk read violations on some devices such as samsung and pixel for android 9/10 - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.onCreate(savedInstanceState) } @@ -757,7 +756,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } override fun attachBaseContext(base: Context) { - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.attachBaseContext(base) } } diff --git a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt index 8b42b5533..3e84fa22a 100644 --- a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt @@ -15,7 +15,6 @@ import org.mozilla.fenix.components.IntentProcessorType import org.mozilla.fenix.components.getType import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor @@ -28,7 +27,7 @@ class IntentReceiverActivity : Activity() { @VisibleForTesting override fun onCreate(savedInstanceState: Bundle?) { // StrictMode violation on certain devices such as Samsung - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.onCreate(savedInstanceState) } @@ -68,7 +67,7 @@ class IntentReceiverActivity : Activity() { ) } // StrictMode violation on certain devices such as Samsung - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { startActivity(intent) } finish() // must finish() after starting the other activity diff --git a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt index 2b9594e50..b52faf9b3 100644 --- a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt +++ b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt @@ -8,6 +8,7 @@ import android.os.Build import android.os.StrictMode import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import mozilla.components.support.ktx.android.os.resetAfter private const val MANUFACTURE_HUAWEI: String = "HUAWEI" private const val MANUFACTURE_ONE_PLUS: String = "OnePlus" @@ -17,9 +18,8 @@ private const val MANUFACTURE_ONE_PLUS: String = "OnePlus" */ class StrictModeManager(config: Config) { - // The expression in this if is duplicated in StrictMode.ThreadPolicy.resetPoliciesAfter - // because we don't want to have to pass in a dependency each time the ext fn is called. - private val isEnabledByBuildConfig = config.channel.isDebug + // This is public so it can be used by inline functions. + val isEnabledByBuildConfig = config.channel.isDebug /*** * Enables strict mode for debug purposes. meant to be run only in the main process. @@ -67,6 +67,28 @@ class StrictModeManager(config: Config) { }, false) } + /** + * Runs the given [functionBlock] and sets the given [StrictMode.ThreadPolicy] after its + * completion when in a build configuration that has StrictMode enabled. If StrictMode is + * not enabled, simply runs the [functionBlock]. + * + * This function is written in the style of [AutoCloseable.use]. + * + * This is significantly less convenient to run than when it was written as an extension function + * on [StrictMode.ThreadPolicy] but I think this is okay: it shouldn't be easy to ignore StrictMode. + * + * @return the value returned by [functionBlock]. + */ + inline fun resetAfter(policy: StrictMode.ThreadPolicy, functionBlock: () -> R): R { + // Calling resetAfter takes 1-2ms (unknown device) so we only execute it if StrictMode can + // actually be enabled. https://github.com/mozilla-mobile/fenix/issues/11617 + return if (isEnabledByBuildConfig) { + policy.resetAfter(functionBlock) + } else { + functionBlock() + } + } + /** * There are certain manufacturers that have custom font classes for the OS systems. * These classes violates the [StrictMode] policies on startup. As a workaround, we create diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index ebd51b122..1642afe09 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -39,7 +39,6 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.navigateSafe import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.shortcut.PwaOnboardingObserver import org.mozilla.fenix.trackingprotection.TrackingProtectionOverlay @@ -110,7 +109,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { ) readerViewFeature.set( - feature = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + feature = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { ReaderViewFeature( context, components.core.engine, diff --git a/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt b/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt index 92fb160bb..bd6c0196c 100644 --- a/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt +++ b/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt @@ -16,7 +16,7 @@ import mozilla.components.concept.sync.OAuthAccount import mozilla.components.lib.crash.CrashReporter import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.support.base.log.logger.Logger -import org.mozilla.fenix.ext.resetPoliciesAfter +import org.mozilla.fenix.StrictModeManager import kotlin.coroutines.CoroutineContext /** @@ -57,6 +57,7 @@ internal abstract class AbnormalFxaEvent : Exception() { class AccountAbnormalities( context: Context, private val crashReporter: CrashReporter, + strictMode: StrictModeManager, private val coroutineContext: CoroutineContext = Dispatchers.IO ) : AccountObserver { companion object { @@ -79,14 +80,14 @@ class AccountAbnormalities( private val hadAccountPrior: Boolean init { - val prefPair = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + val prefPair = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { val p = context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE) val a = p.getBoolean(KEY_HAS_ACCOUNT, false) Pair(p, a) } prefs = prefPair.first hadAccountPrior = prefPair.second -} + } /** * Once [accountManager] is initialized, queries it to detect abnormal account states. diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index 120a27ac1..9f9bd201b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -18,6 +18,7 @@ import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.ReleaseChannel +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.components.metrics.AdjustMetricsService import org.mozilla.fenix.components.metrics.GleanMetricsService import org.mozilla.fenix.components.metrics.LeanplumMetricsService @@ -34,7 +35,8 @@ import org.mozilla.geckoview.BuildConfig.MOZ_UPDATE_CHANNEL */ @Mockable class Analytics( - private val context: Context + private val context: Context, + strictMode: StrictModeManager ) { val crashReporter: CrashReporter by lazy { val services = mutableListOf() @@ -84,7 +86,10 @@ class Analytics( ) } - val leanplumMetricsService by lazy { LeanplumMetricsService(context as Application) } + val leanplumMetricsService by lazy { LeanplumMetricsService( + context as Application, + strictMode + ) } val metrics: MetricController by lazy { MetricController.create( diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index c2e92057e..61d6c08cd 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -36,6 +36,7 @@ import mozilla.components.service.sync.logins.SyncableLoginsStorage import mozilla.components.support.utils.RunWhenReadyQueue import org.mozilla.fenix.Config import org.mozilla.fenix.R +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.components @@ -57,7 +58,8 @@ class BackgroundServices( historyStorage: Lazy, bookmarkStorage: Lazy, passwordsStorage: Lazy, - remoteTabsStorage: Lazy + remoteTabsStorage: Lazy, + strictMode: StrictModeManager ) { // Allows executing tasks which depend on the account manager, but do not need to eagerly initialize it. val accountManagerAvailableQueue = RunWhenReadyQueue() @@ -105,7 +107,7 @@ class BackgroundServices( context.components.analytics.metrics ) - val accountAbnormalities = AccountAbnormalities(context, crashReporter) + val accountAbnormalities = AccountAbnormalities(context, crashReporter, strictMode) val accountManager by lazy { makeAccountManager(context, serverConfig, deviceConfig, syncConfig) } diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 8bcbce420..83ac7fa94 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -43,11 +43,12 @@ class Components(private val context: Context) { core.lazyHistoryStorage, core.lazyBookmarksStorage, core.lazyPasswordsStorage, - core.lazyRemoteTabsStorage + core.lazyRemoteTabsStorage, + strictMode ) } val services by lazy { Services(context, backgroundServices.accountManager) } - val core by lazy { Core(context, analytics.crashReporter) } + val core by lazy { Core(context, analytics.crashReporter, strictMode) } val search by lazy { Search(context) } val useCases by lazy { UseCases( @@ -120,7 +121,7 @@ class Components(private val context: Context) { AddonManager(core.store, core.engine, addonCollectionProvider, addonUpdater) } - val analytics by lazy { Analytics(context) } + val analytics by lazy { Analytics(context, strictMode) } val publicSuffixList by lazy { PublicSuffixList(context) } val clipboardHandler by lazy { ClipboardHandler(context) } val migrationStore by lazy { MigrationStore() } diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index a2ae6f888..86fe8db4e 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -60,9 +60,9 @@ import org.mozilla.fenix.AppRequestInterceptor import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.downloads.DownloadService import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.media.MediaService import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry @@ -77,7 +77,11 @@ import java.util.concurrent.TimeUnit * Component group for all core browser functionality. */ @Mockable -class Core(private val context: Context, private val crashReporter: CrashReporting) { +class Core( + private val context: Context, + private val crashReporter: CrashReporting, + strictMode: StrictModeManager +) { /** * The browser engine component initialized based on the build * configuration (see build variants). @@ -268,7 +272,11 @@ class Core(private val context: Context, private val crashReporter: CrashReporti val bookmarksStorage by lazy { lazyBookmarksStorage.value } val passwordsStorage by lazy { lazyPasswordsStorage.value } - val tabCollectionStorage by lazy { TabCollectionStorage(context, sessionManager) } + val tabCollectionStorage by lazy { TabCollectionStorage( + context, + sessionManager, + strictMode + ) } /** * A storage component for persisting thumbnail images of tabs. @@ -280,7 +288,7 @@ class Core(private val context: Context, private val crashReporter: CrashReporti val topSitesStorage by lazy { val defaultTopSites = mutableListOf>() - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { if (!context.settings().defaultTopSitesAdded) { defaultTopSites.add( Pair( diff --git a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt index 10523589d..2ff36f047 100644 --- a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt +++ b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt @@ -19,8 +19,8 @@ import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollectionStorage import mozilla.components.support.base.observer.Observable import mozilla.components.support.base.observer.ObserverRegistry +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder import org.mozilla.fenix.utils.Mockable @@ -29,6 +29,7 @@ import org.mozilla.fenix.utils.Mockable class TabCollectionStorage( private val context: Context, private val sessionManager: SessionManager, + strictMode: StrictModeManager, private val delegate: Observable = ObserverRegistry() ) : Observable by delegate { @@ -56,7 +57,7 @@ class TabCollectionStorage( var cachedTabCollections = listOf() private val collectionStorage by lazy { - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { TabCollectionStorage(context, sessionManager) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt index d3dc2b392..3012e1aa1 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt @@ -22,8 +22,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.BuildConfig +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.components.metrics.MozillaProductDetector.MozillaProducts -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor import java.util.Locale @@ -58,6 +58,7 @@ private val Event.name: String? class LeanplumMetricsService( private val application: Application, + strictMode: StrictModeManager, private val deviceIdGenerator: () -> String = { randomUUID().toString() } ) : MetricsService, DeepLinkIntentProcessor.DeepLinkVerifier { val scope = CoroutineScope(Dispatchers.IO) @@ -83,7 +84,7 @@ class LeanplumMetricsService( override val type = MetricServiceType.Marketing private val token = Token(LeanplumId, LeanplumToken) - private val preferences = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + private val preferences = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { application.getSharedPreferences(PREFERENCE_NAME, MODE_PRIVATE) } diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 707c584ee..97fb25474 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -91,7 +91,6 @@ import org.mozilla.fenix.ext.hideToolbar import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor @@ -148,7 +147,7 @@ class HomeFragment : Fragment() { get() = requireComponents.core.store private val onboarding by lazy { - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + requireComponents.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { FenixOnboarding(requireContext()) } } @@ -198,7 +197,7 @@ class HomeFragment : Fragment() { expandedCollections = emptySet(), mode = currentMode.getCurrentMode(), topSites = components.core.topSitesStorage.cachedTopSites, - tip = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + tip = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { FenixTipManager( listOf( MasterPasswordTipProvider( @@ -538,7 +537,7 @@ class HomeFragment : Fragment() { collections = components.core.tabCollectionStorage.cachedTabCollections, mode = currentMode.getCurrentMode(), topSites = components.core.topSitesStorage.cachedTopSites, - tip = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + tip = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { FenixTipManager( listOf( MasterPasswordTipProvider( diff --git a/app/src/main/java/org/mozilla/fenix/whatsnew/WhatsNew.kt b/app/src/main/java/org/mozilla/fenix/whatsnew/WhatsNew.kt index a536f593a..dd586ab47 100644 --- a/app/src/main/java/org/mozilla/fenix/whatsnew/WhatsNew.kt +++ b/app/src/main/java/org/mozilla/fenix/whatsnew/WhatsNew.kt @@ -6,7 +6,7 @@ package org.mozilla.fenix.whatsnew import android.content.Context import android.os.StrictMode -import org.mozilla.fenix.ext.resetPoliciesAfter +import org.mozilla.fenix.ext.components // This file is a modified port from Focus Android @@ -70,7 +70,7 @@ class WhatsNew private constructor(private val storage: WhatsNewStorage) { fun shouldHighlightWhatsNew(context: Context): Boolean { return shouldHighlightWhatsNew( ContextWhatsNewVersion(context), - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + context.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { SharedPreferenceWhatsNewStorage(context) } ) diff --git a/app/src/test/java/org/mozilla/fenix/IntentReceiverActivityTest.kt b/app/src/test/java/org/mozilla/fenix/IntentReceiverActivityTest.kt index 1bb8783c3..a94d79414 100644 --- a/app/src/test/java/org/mozilla/fenix/IntentReceiverActivityTest.kt +++ b/app/src/test/java/org/mozilla/fenix/IntentReceiverActivityTest.kt @@ -202,6 +202,7 @@ class IntentReceiverActivityTest { every { activity.settings() } returns settings every { activity.components.analytics } returns mockk(relaxed = true) every { activity.components.intentProcessors } returns intentProcessors + every { activity.components.strictMode } returns mockk(relaxed = true) } private inline fun mockIntentProcessor(): T { diff --git a/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt b/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt index 22a8df8ed..c69007c10 100644 --- a/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt @@ -25,7 +25,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk() // no account present - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true)) try { accountAbnormalities.userRequestedLogout() @@ -52,7 +52,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk(relaxed = true) val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) // Logout action must be preceded by auth. @@ -65,7 +65,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk(relaxed = true) val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) accountAbnormalities.onAuthenticated(mockk(), mockk()) @@ -83,7 +83,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk(relaxed = true) val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) // User didn't request this logout. @@ -96,7 +96,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk(relaxed = true) val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) accountAbnormalities.onAuthenticated(mockk(), mockk()) @@ -104,7 +104,7 @@ class AccountAbnormalitiesTest { every { accountManager.authenticatedAccount() } returns null // Pretend we restart, and instantiate a new middleware instance. - val accountAbnormalities2 = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities2 = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) // mock accountManager doesn't have an account, but we expect it to have one since we // were authenticated before our "restart". accountAbnormalities2.accountManagerStarted(accountManager) @@ -117,7 +117,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk() val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) // We saw an auth event, then user requested a logout. diff --git a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt index 1d31eaf56..a2d1f92c5 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt @@ -28,7 +28,7 @@ class TestComponents(private val context: Context) : Components(context) { ) } override val intentProcessors by lazy { mockk(relaxed = true) } - override val analytics by lazy { Analytics(context) } + override val analytics by lazy { Analytics(context, strictMode) } override val clipboardHandler by lazy { ClipboardHandler(context) } diff --git a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt index fad4bd4b9..70dccaebd 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt @@ -17,7 +17,11 @@ import mozilla.components.concept.fetch.Client import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.top.sites.DefaultTopSitesStorage -class TestCore(context: Context, crashReporter: CrashReporting) : Core(context, crashReporter) { +class TestCore(context: Context, crashReporter: CrashReporting) : Core( + context, + crashReporter, + mockk() +) { override val engine = mockk(relaxed = true) { every { this@mockk getProperty "settings" } returns mockk(relaxed = true) diff --git a/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt b/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt index 6027a100a..ea0e90302 100644 --- a/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.components.metrics import android.content.Context.MODE_PRIVATE +import io.mockk.mockk import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -30,10 +31,18 @@ class LeanplumMetricsServiceTest { assertNull(sharedPreferences.getString("LP_DEVICE_ID", null)) - val leanplumMetricService = LeanplumMetricsService(testContext.application, idGenerator) + val leanplumMetricService = LeanplumMetricsService( + testContext.application, + mockk(relaxed = true), + idGenerator + ) assertEquals("TEST_DEVICE_ID", leanplumMetricService.deviceId) - val leanplumMetricService2 = LeanplumMetricsService(testContext.application, idGenerator) + val leanplumMetricService2 = LeanplumMetricsService( + testContext.application, + mockk(relaxed = true), + idGenerator + ) assertEquals("TEST_DEVICE_ID", leanplumMetricService2.deviceId) assertEquals(1, callCount) From dd73cb628bf46b8d85a1b551bea9724dd98939d2 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Mon, 28 Sep 2020 14:05:49 -0700 Subject: [PATCH 15/30] For #13959: clean up existing StrictMode test names. --- .../test/java/org/mozilla/fenix/StrictModeManagerTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt index 5c5601c9d..61a14ef1f 100644 --- a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt @@ -49,21 +49,21 @@ class StrictModeManagerTest { } @Test - fun `test enableStrictMode in release`() { + fun `GIVEN we're in a release build WHEN we enable strict mode THEN we don't set policies`() { releaseManager.enableStrictMode(false) verify(exactly = 0) { StrictMode.setThreadPolicy(any()) } verify(exactly = 0) { StrictMode.setVmPolicy(any()) } } @Test - fun `test enableStrictMode in debug`() { + fun `GIVEN we're in a debug build WHEN we enable strict mode THEN we set policies`() { debugManager.enableStrictMode(false) verify { StrictMode.setThreadPolicy(any()) } verify { StrictMode.setVmPolicy(any()) } } @Test - fun `test changeStrictModePolicies in debug`() { + fun `GIVEN we're in a debug build WHEN we attach a listener THEN we attach to the fragment lifecycle and detach when onFragmentResumed is called`() { val callbacks = slot() debugManager.attachListenerToDisablePenaltyDeath(fragmentManager) From 42cca072e2a06a994fe8e3d23f8aa7d580b4d335 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Mon, 28 Sep 2020 14:22:30 -0700 Subject: [PATCH 16/30] For #13959: remove resetAfter & port tests to StrictModeManager. --- .../java/org/mozilla/fenix/ext/StrictMode.kt | 28 ------- .../mozilla/fenix/StrictModeManagerTest.kt | 40 ++++++++++ .../org/mozilla/fenix/ext/StrictModeTest.kt | 79 ------------------- 3 files changed, 40 insertions(+), 107 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/ext/StrictModeTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt b/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt deleted file mode 100644 index 54985108f..000000000 --- a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.ext - -import android.os.StrictMode -import mozilla.components.support.ktx.android.os.resetAfter -import org.mozilla.fenix.Config - -/** - * Runs the given [functionBlock] and sets the ThreadPolicy after its completion in Debug mode. - * Otherwise simply runs the [functionBlock] - * This function is written in the style of [AutoCloseable.use]. - * @return the value returned by [functionBlock]. - */ -inline fun StrictMode.ThreadPolicy.resetPoliciesAfter(functionBlock: () -> R): R { - // Calling resetAfter takes 1-2ms (unknown device) so we only execute it if StrictMode can - // actually be enabled. https://github.com/mozilla-mobile/fenix/issues/11617 - // - // The expression in this if is duplicated in StrictModeManager.enableStrictMode: see that method - // for details. - return if (Config.channel.isDebug) { - resetAfter { functionBlock() } - } else { - functionBlock() - } -} diff --git a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt index 61a14ef1f..f4f3e21a8 100644 --- a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt @@ -16,6 +16,8 @@ import io.mockk.slot import io.mockk.unmockkStatic import io.mockk.verify import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -73,4 +75,42 @@ class StrictModeManagerTest { callbacks.captured.onFragmentResumed(fragmentManager, mockk()) verify { fragmentManager.unregisterFragmentLifecycleCallbacks(callbacks.captured) } } + + @Test + fun `GIVEN we're in a release build WHEN resetAfter is called THEN we return the value from the function block`() { + val expected = "Hello world" + val actual = releaseManager.resetAfter(StrictMode.allowThreadDiskReads()) { expected } + assertEquals(expected, actual) + } + + @Test + fun `GIVEN we're in a debug build WHEN resetAfter is called THEN we return the value from the function block`() { + val expected = "Hello world" + val actual = debugManager.resetAfter(StrictMode.allowThreadDiskReads()) { expected } + assertEquals(expected, actual) + } + + @Test + fun `GIVEN we're in a release build WHEN resetAfter is called THEN the old policy is not set`() { + releaseManager.resetAfter(StrictMode.allowThreadDiskReads()) { "" } + verify(exactly = 0) { StrictMode.setThreadPolicy(any()) } + } + + @Test + fun `GIVEN we're in a debug build WHEN resetAfter is called THEN the old policy is set`() { + val expectedPolicy = StrictMode.allowThreadDiskReads() + debugManager.resetAfter(expectedPolicy) { "" } + verify { StrictMode.setThreadPolicy(expectedPolicy) } + } + + @Test + fun `GIVEN we're in a debug build WHEN resetAfter is called and an exception is thrown from the function THEN the old policy is set`() { + val expectedPolicy = StrictMode.allowThreadDiskReads() + try { + debugManager.resetAfter(expectedPolicy) { throw IllegalStateException() } + @Suppress("UNREACHABLE_CODE") fail("Expected previous method to throw.") + } catch (e: IllegalStateException) { /* Do nothing */ } + + verify { StrictMode.setThreadPolicy(expectedPolicy) } + } } diff --git a/app/src/test/java/org/mozilla/fenix/ext/StrictModeTest.kt b/app/src/test/java/org/mozilla/fenix/ext/StrictModeTest.kt deleted file mode 100644 index 26854c46d..000000000 --- a/app/src/test/java/org/mozilla/fenix/ext/StrictModeTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.ext - -import android.os.StrictMode -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.mockkStatic -import io.mockk.unmockkObject -import io.mockk.unmockkStatic -import io.mockk.verify -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.Config -import org.mozilla.fenix.ReleaseChannel -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@RunWith(FenixRobolectricTestRunner::class) -class StrictModeTest { - - private lateinit var threadPolicy: StrictMode.ThreadPolicy - private lateinit var functionBlock: () -> String - - @Before - fun setup() { - threadPolicy = StrictMode.ThreadPolicy.LAX - functionBlock = mockk() - mockkStatic(StrictMode::class) - mockkObject(Config) - - every { StrictMode.setThreadPolicy(threadPolicy) } just Runs - every { functionBlock() } returns "Hello world" - } - - @After - fun teardown() { - unmockkStatic(StrictMode::class) - unmockkObject(Config) - } - - @Test - fun `runs function block in release`() { - every { Config.channel } returns ReleaseChannel.Release - assertEquals("Hello world", threadPolicy.resetPoliciesAfter(functionBlock)) - verify(exactly = 0) { StrictMode.setThreadPolicy(any()) } - } - - @Test - fun `runs function block in debug`() { - every { Config.channel } returns ReleaseChannel.Debug - assertEquals("Hello world", threadPolicy.resetPoliciesAfter(functionBlock)) - verify { StrictMode.setThreadPolicy(threadPolicy) } - } - - @Test - fun `sets thread policy even if function throws`() { - every { Config.channel } returns ReleaseChannel.Debug - every { functionBlock() } throws IllegalStateException() - var exception: IllegalStateException? = null - - try { - threadPolicy.resetPoliciesAfter(functionBlock) - } catch (e: IllegalStateException) { - exception = e - } - - verify { StrictMode.setThreadPolicy(threadPolicy) } - assertNotNull(exception) - } -} From d767cd199ede1364d5f69dc7e7ddc7d4fbb9b24a Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Mon, 28 Sep 2020 15:42:14 -0700 Subject: [PATCH 17/30] For #13959: fix startup crash by using arg Context. The `context` member function returns null in attachBaseContext so we need to use the Context that's being attached instead. --- app/src/main/java/org/mozilla/fenix/HomeActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index b0615d7da..333fb7ba5 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -756,7 +756,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } override fun attachBaseContext(base: Context) { - components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { + base.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.attachBaseContext(base) } } From bbaca062741fdfb9044fca3ef1f2853056f0d057 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Wed, 19 Aug 2020 10:20:06 -0700 Subject: [PATCH 18/30] For #12565: Pass metrics to share controller --- .../mozilla/fenix/share/ShareController.kt | 7 +- .../org/mozilla/fenix/share/ShareFragment.kt | 1 + .../fenix/share/ShareControllerTest.kt | 133 ++++++++++-------- 3 files changed, 77 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt index e3c1a7310..35635caf1 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt @@ -29,7 +29,7 @@ import mozilla.components.support.ktx.kotlin.isExtensionUrl import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.ext.metrics +import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.nav import org.mozilla.fenix.share.listadapters.AppShareOption @@ -66,6 +66,7 @@ interface ShareController { @Suppress("TooManyFunctions") class DefaultShareController( private val context: Context, + private val metrics: MetricController, private val shareSubject: String?, private val shareData: List, private val sendTabUseCases: SendTabUseCases, @@ -122,7 +123,7 @@ class DefaultShareController( } override fun handleShareToDevice(device: Device) { - context.metrics.track(Event.SendTab) + metrics.track(Event.SendTab) shareToDevicesWithRetry { sendTabUseCases.sendToDeviceAsync(device.id, shareData.toTabData()) } } @@ -131,7 +132,7 @@ class DefaultShareController( } override fun handleSignIn() { - context.metrics.track(Event.SignInToSendTab) + metrics.track(Event.SignInToSendTab) val directions = ShareFragmentDirections.actionGlobalTurnOnSync(padSnackbar = true) navController.nav(R.id.shareFragment, directions) diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt b/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt index cd190d564..0a4ed5ae0 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt @@ -67,6 +67,7 @@ class ShareFragment : AppCompatDialogFragment() { shareInteractor = ShareInteractor( DefaultShareController( context = requireContext(), + metrics = requireComponents.analytics.metrics, shareSubject = args.shareSubject, shareData = shareData, snackbar = FenixSnackbar.make( diff --git a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt index 90540b24c..9950dd5b6 100644 --- a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt @@ -10,12 +10,13 @@ import android.content.Context import android.content.Intent import androidx.navigation.NavController import com.google.android.material.snackbar.Snackbar +import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.slot -import io.mockk.spyk import io.mockk.verify import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,7 +39,6 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.share.listadapters.AppShareOption @@ -46,9 +46,15 @@ import org.mozilla.fenix.share.listadapters.AppShareOption @RunWith(FenixRobolectricTestRunner::class) @ExperimentalCoroutinesApi class ShareControllerTest { - // Need a valid context to retrieve Strings for example, but we also need it to return our "metrics" - private val context: Context = spyk(testContext) - private val metrics: MetricController = mockk(relaxed = true) + + @MockK(relaxed = true) private lateinit var metrics: MetricController + @MockK(relaxed = true) private lateinit var sendTabUseCases: SendTabUseCases + @MockK(relaxed = true) private lateinit var snackbar: FenixSnackbar + @MockK(relaxed = true) private lateinit var navController: NavController + @MockK(relaxed = true) private lateinit var dismiss: (ShareController.Result) -> Unit + @MockK private lateinit var recentAppStorage: RecentAppsStorage + + private val context: Context = testContext private val shareSubject = "shareSubject" private val shareData = listOf( ShareData(url = "url0", title = "title0"), @@ -61,23 +67,15 @@ class ShareControllerTest { ) private val textToShare = "${shareData[0].url}\n\n${shareData[1].url}" private val testCoroutineScope = TestCoroutineScope() - private val sendTabUseCases = mockk(relaxed = true) - private val snackbar = mockk(relaxed = true) - private val navController = mockk(relaxed = true) - private val dismiss = mockk<(ShareController.Result) -> Unit>(relaxed = true) - private val recentAppStorage = mockk(relaxed = true) - private val controller = DefaultShareController( - context, shareSubject, shareData, sendTabUseCases, snackbar, navController, - recentAppStorage, testCoroutineScope, dismiss - ) @Before fun setUp() { - every { context.metrics } returns metrics + MockKAnnotations.init(this) } @Test fun `handleShareClosed should call a passed in delegate to close this`() { + val controller = buildController() controller.handleShareClosed() verify { dismiss(ShareController.Result.DISMISSED) } @@ -85,17 +83,20 @@ class ShareControllerTest { @Test fun `handleShareToApp should start a new sharing activity and close this`() = runBlocking { - val appPackageName = "package" - val appClassName = "activity" - val appShareOption = AppShareOption("app", mockk(), appPackageName, appClassName) + val appShareOption = AppShareOption( + name = "app", + icon = mockk(), + packageName = "package", + activityName = "activity" + ) val shareIntent = slot() // Our share Intent uses `FLAG_ACTIVITY_NEW_TASK` but when resolving the startActivity call // needed for capturing the actual Intent used the `slot` one doesn't have this flag so we // need to use an Activity Context. - val activityContext: Context = mockk() - val testController = DefaultShareController(activityContext, shareSubject, shareData, mockk(), - mockk(), mockk(), recentAppStorage, testCoroutineScope, dismiss) - every { activityContext.startActivity(capture(shareIntent)) } just Runs + val activityContext: Context = mockk { + every { startActivity(capture(shareIntent)) } just Runs + } + val testController = buildController(context = activityContext) every { recentAppStorage.updateRecentApp(appShareOption.activityName) } just Runs testController.handleShareToApp(appShareOption) @@ -107,8 +108,8 @@ class ShareControllerTest { assertEquals(textToShare, shareIntent.captured.extras!![Intent.EXTRA_TEXT]) assertEquals("text/plain", shareIntent.captured.type) assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, shareIntent.captured.flags) - assertEquals(appPackageName, shareIntent.captured.component!!.packageName) - assertEquals(appClassName, shareIntent.captured.component!!.className) + assertEquals("package", shareIntent.captured.component!!.packageName) + assertEquals("activity", shareIntent.captured.component!!.className) verify { recentAppStorage.updateRecentApp(appShareOption.activityName) } verifyOrder { @@ -119,18 +120,21 @@ class ShareControllerTest { @Test fun `handleShareToApp should dismiss with an error start when a security exception occurs`() { - val appPackageName = "package" - val appClassName = "activity" - val appShareOption = AppShareOption("app", mockk(), appPackageName, appClassName) + val appShareOption = AppShareOption( + name = "app", + icon = mockk(), + packageName = "package", + activityName = "activity" + ) val shareIntent = slot() // Our share Intent uses `FLAG_ACTIVITY_NEW_TASK` but when resolving the startActivity call // needed for capturing the actual Intent used the `slot` one doesn't have this flag so we // need to use an Activity Context. - val activityContext: Context = mockk() - val testController = DefaultShareController(activityContext, shareSubject, shareData, mockk(), - snackbar, mockk(), mockk(), testCoroutineScope, dismiss) - every { activityContext.startActivity(capture(shareIntent)) } throws SecurityException() - every { activityContext.getString(R.string.share_error_snackbar) } returns "Cannot share to this app" + val activityContext: Context = mockk { + every { startActivity(capture(shareIntent)) } throws SecurityException() + every { getString(R.string.share_error_snackbar) } returns "Cannot share to this app" + } + val testController = buildController(context = activityContext) testController.handleShareToApp(appShareOption) @@ -175,6 +179,7 @@ class ShareControllerTest { val deviceId = slot() val tabsShared = slot>() + val controller = buildController() controller.handleShareToDevice(deviceToShareTo) // Verify all the needed methods are called. @@ -199,6 +204,7 @@ class ShareControllerTest { ) val tabsShared = slot>() + val controller = buildController() controller.handleShareToAllDevices(devicesToShareTo) verifyOrder { @@ -213,6 +219,7 @@ class ShareControllerTest { @Test fun `handleSignIn should navigate to the Sync Fragment and dismiss this one`() { + val controller = buildController() controller.handleSignIn() verifyOrder { @@ -227,6 +234,7 @@ class ShareControllerTest { @Test fun `handleReauth should navigate to the Account Problem Fragment and dismiss this one`() { + val controller = buildController() controller.handleReauth() verifyOrder { @@ -240,6 +248,7 @@ class ShareControllerTest { @Test fun `showSuccess should show a snackbar with a success message`() { + val controller = buildController() val expectedMessage = controller.getSuccessMessage() val expectedTimeout = Snackbar.LENGTH_SHORT @@ -259,7 +268,7 @@ class ShareControllerTest { val expectedRetryMessage = context.getString(R.string.sync_sent_tab_error_snackbar_action) - controller.showFailureWithRetryOption(operation) + buildController().showFailureWithRetryOption(operation) verify { snackbar.apply { @@ -273,18 +282,12 @@ class ShareControllerTest { @Test fun `getSuccessMessage should return different strings depending on the number of shared tabs`() { - val controllerWithOneSharedTab = DefaultShareController( - context, - shareSubject, - listOf(ShareData(url = "url0", title = "title0")), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk() + val controllerWithOneSharedTab = buildController( + data = listOf(ShareData(url = "url0", title = "title0")) + ) + val controllerWithMoreSharedTabs = buildController( + data = shareData ) - val controllerWithMoreSharedTabs = controller val expectedTabSharedMessage = context.getString(R.string.sync_sent_tab_snackbar) val expectedTabsSharedMessage = context.getString(R.string.sync_sent_tabs_snackbar) @@ -298,7 +301,7 @@ class ShareControllerTest { @Test fun `getShareText should respect concatenate shared tabs urls`() { - assertEquals(textToShare, controller.getShareText()) + assertEquals(textToShare, buildController(data = shareData).getShareText()) } @Test @@ -308,10 +311,7 @@ class ShareControllerTest { ShareData(url = "moz-extension://eb8df45a-895b-4f3a-896a-c0c71ae5/page.html?url=url0"), ShareData(url = "url1") ) - val controller = DefaultShareController( - context, shareSubject, shareData, sendTabUseCases, snackbar, navController, - recentAppStorage, testCoroutineScope, dismiss - ) + val controller = buildController(data = shareData) val expectedShareText = "${shareData[0].url}\n\nurl0\n\n${shareData[2].url}" assertEquals(expectedShareText, controller.getShareText()) @@ -319,25 +319,20 @@ class ShareControllerTest { @Test fun `getShareSubject will return "shareSubject" if that is non null`() { - assertEquals(shareSubject, controller.getShareSubject()) + assertEquals(shareSubject, buildController(subject = shareSubject).getShareSubject()) } @Test fun `getShareSubject will return a concatenation of tab titles if "shareSubject" is null`() { - val controller = DefaultShareController( - context, null, shareData, sendTabUseCases, snackbar, navController, - recentAppStorage, testCoroutineScope, dismiss - ) + val controller = buildController(subject = null) assertEquals("title0, title1", controller.getShareSubject()) } @Test fun `ShareTab#toTabData maps a list of ShareTab to a TabData list`() { - var tabData: List - - with(controller) { - tabData = shareData.toTabData() + val tabData = with(buildController()) { + shareData.toTabData() } assertEquals(tabsData, tabData) @@ -345,14 +340,13 @@ class ShareControllerTest { @Test fun `ShareTab#toTabData creates a data url from text if no url is specified`() { - var tabData: List val expected = listOf( TabData(title = "title0", url = ""), TabData(title = "title1", url = "data:,Hello%2C%20World!") ) - with(controller) { - tabData = listOf( + val tabData = with(buildController()) { + listOf( ShareData(title = "title0"), ShareData(title = "title1", text = "Hello, World!") ).toTabData() @@ -360,4 +354,21 @@ class ShareControllerTest { assertEquals(expected, tabData) } + + private fun buildController( + context: Context = this.context, + subject: String? = shareSubject, + data: List = shareData + ) = DefaultShareController( + context = context, + metrics = metrics, + shareSubject = subject, + shareData = data, + sendTabUseCases = sendTabUseCases, + snackbar = snackbar, + navController = navController, + recentAppsStorage = recentAppStorage, + viewLifecycleScope = testCoroutineScope, + dismiss = dismiss + ) } From 3c22100b8495434ea8c30f9cef2bb8ce554f180b Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Wed, 19 Aug 2020 10:47:40 -0700 Subject: [PATCH 19/30] For #12565: Pass bookmark storage to controller --- .../library/bookmarks/BookmarkController.kt | 16 +++-- .../library/bookmarks/BookmarkFragment.kt | 2 + .../bookmarks/BookmarkControllerTest.kt | 72 +++++++++---------- .../fenix/share/ShareControllerTest.kt | 10 +-- 4 files changed, 52 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt index 842d1878c..cdf019aab 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt @@ -14,14 +14,14 @@ import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.concept.storage.BookmarkNode +import mozilla.components.concept.storage.BookmarksStorage +import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.sync.SyncReason import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.ext.bookmarkStorage -import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav /** @@ -52,6 +52,8 @@ interface BookmarkController { @Suppress("TooManyFunctions") class DefaultBookmarkController( private val activity: HomeActivity, + private val bookmarkStorage: BookmarksStorage, + private val accountManager: FxaAccountManager, private val navController: NavController, private val clipboardManager: ClipboardManager?, private val scope: CoroutineScope, @@ -143,14 +145,14 @@ class DefaultBookmarkController( scope.launch { store.dispatch(BookmarkFragmentAction.StartSync) invokePendingDeletion() - activity.components.backgroundServices.accountManager.syncNow(SyncReason.User) + accountManager.syncNow(SyncReason.User) // The current bookmark node we are viewing may be made invalid after syncing so we // check if the current node is valid and if it isn't we find the nearest valid ancestor // and open it val validAncestorGuid = store.state.guidBackstack.findLast { guid -> - activity.bookmarkStorage.getBookmark(guid) != null + bookmarkStorage.getBookmark(guid) != null } ?: BookmarkRoot.Mobile.id - val node = activity.bookmarkStorage.getBookmark(validAncestorGuid)!! + val node = bookmarkStorage.getBookmark(validAncestorGuid)!! handleBookmarkExpand(node) store.dispatch(BookmarkFragmentAction.FinishSync) } @@ -160,12 +162,12 @@ class DefaultBookmarkController( invokePendingDeletion.invoke() scope.launch { val parentGuid = store.state.guidBackstack.findLast { guid -> - store.state.tree?.guid != guid && activity.bookmarkStorage.getBookmark(guid) != null + store.state.tree?.guid != guid && bookmarkStorage.getBookmark(guid) != null } if (parentGuid == null) { navController.popBackStack() } else { - val parent = activity.bookmarkStorage.getBookmark(parentGuid)!! + val parent = bookmarkStorage.getBookmark(parentGuid)!! handleBookmarkExpand(parent) } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt index 18c19943e..054635f70 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt @@ -88,6 +88,8 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan _bookmarkInteractor = BookmarkFragmentInteractor( bookmarksController = DefaultBookmarkController( activity = requireActivity() as HomeActivity, + bookmarkStorage = requireComponents.core.bookmarksStorage, + accountManager = requireComponents.backgroundServices.accountManager, navController = findNavController(), clipboardManager = requireContext().getSystemService(), scope = viewLifecycleOwner.lifecycleScope, diff --git a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt index 2d0f2deb4..dcc41ec74 100644 --- a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt @@ -6,20 +6,17 @@ package org.mozilla.fenix.library.bookmarks import android.content.ClipData import android.content.ClipboardManager -import android.content.Context import androidx.navigation.NavController -import androidx.navigation.NavDestination import androidx.navigation.NavDirections -import io.mockk.Runs +import io.mockk.MockKAnnotations import io.mockk.called import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.runs -import io.mockk.slot -import io.mockk.spyk import io.mockk.verify import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -27,6 +24,8 @@ import kotlinx.coroutines.test.TestCoroutineScope import mozilla.appservices.places.BookmarkRoot import mozilla.components.concept.storage.BookmarkNode import mozilla.components.concept.storage.BookmarkNodeType +import mozilla.components.concept.storage.BookmarksStorage +import mozilla.components.service.fxa.manager.FxaAccountManager import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -35,31 +34,27 @@ import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode -import org.mozilla.fenix.components.Services import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.ext.bookmarkStorage -import org.mozilla.fenix.ext.components -@Suppress("TooManyFunctions", "LargeClass") @ExperimentalCoroutinesApi class BookmarkControllerTest { - private lateinit var controller: BookmarkController - - private val bookmarkStore = spyk(BookmarkFragmentStore(BookmarkFragmentState(null))) - private val context: Context = mockk(relaxed = true) private val scope = TestCoroutineScope() - private val clipboardManager: ClipboardManager = mockk(relaxUnitFun = true) - private val navController: NavController = mockk(relaxed = true) - private val sharedViewModel: BookmarksSharedViewModel = mockk() - private val loadBookmarkNode: suspend (String) -> BookmarkNode? = mockk(relaxed = true) - private val showSnackbar: (String) -> Unit = mockk(relaxed = true) - private val deleteBookmarkNodes: (Set, Event) -> Unit = mockk(relaxed = true) - private val deleteBookmarkFolder: (Set) -> Unit = mockk(relaxed = true) - private val invokePendingDeletion: () -> Unit = mockk(relaxed = true) - - private val homeActivity: HomeActivity = mockk(relaxed = true) - private val services: Services = mockk(relaxed = true) + + @MockK private lateinit var bookmarkStore: BookmarkFragmentStore + @MockK private lateinit var sharedViewModel: BookmarksSharedViewModel + @MockK(relaxUnitFun = true) private lateinit var clipboardManager: ClipboardManager + @MockK(relaxed = true) private lateinit var homeActivity: HomeActivity + @MockK(relaxed = true) private lateinit var bookmarkStorage: BookmarksStorage + @MockK(relaxed = true) private lateinit var accountManager: FxaAccountManager + @MockK(relaxed = true) private lateinit var navController: NavController + @MockK(relaxed = true) private lateinit var loadBookmarkNode: suspend (String) -> BookmarkNode? + @MockK(relaxed = true) private lateinit var showSnackbar: (String) -> Unit + @MockK(relaxed = true) private lateinit var deleteBookmarkNodes: (Set, Event) -> Unit + @MockK(relaxed = true) private lateinit var deleteBookmarkFolder: (Set) -> Unit + @MockK(relaxed = true) private lateinit var invokePendingDeletion: () -> Unit + + private lateinit var controller: BookmarkController private val item = BookmarkNode(BookmarkNodeType.ITEM, "456", "123", 0, "Mozilla", "http://mozilla.org", null) @@ -89,15 +84,20 @@ class BookmarkControllerTest { @Before fun setup() { - every { homeActivity.components.services } returns services - every { navController.currentDestination } returns NavDestination("").apply { - id = R.id.bookmarkFragment + MockKAnnotations.init(this) + loadBookmarkNode = mockk(relaxed = true) + + every { navController.currentDestination } returns mockk { + every { id } returns R.id.bookmarkFragment } + every { bookmarkStore.state } returns BookmarkFragmentState(null) every { bookmarkStore.dispatch(any()) } returns mockk() every { sharedViewModel.selectedFolder = any() } just runs controller = DefaultBookmarkController( activity = homeActivity, + bookmarkStorage = bookmarkStorage, + accountManager = accountManager, navController = navController, clipboardManager = clipboardManager, scope = scope, @@ -211,12 +211,12 @@ class BookmarkControllerTest { @Test fun `handleBookmarkSelected should show a toast when selecting a root folder`() { - val errorMessage = context.getString(R.string.bookmark_cannot_edit_root) + every { homeActivity.resources.getString(R.string.bookmark_cannot_edit_root) } returns "Can't edit default folders" controller.handleBookmarkSelected(root) verify { - showSnackbar(errorMessage) + showSnackbar("Can't edit default folders") } } @@ -240,25 +240,24 @@ class BookmarkControllerTest { @Test fun `handleCopyUrl should copy bookmark url to clipboard and show a toast`() { - val urlCopiedMessage = context.getString(R.string.url_copied) + every { homeActivity.resources.getString(R.string.url_copied) } returns "URL copied" controller.handleCopyUrl(item) verifyOrder { ClipData.newPlainText(item.url, item.url) - showSnackbar(urlCopiedMessage) + showSnackbar("URL copied") } } @Test fun `handleBookmarkSharing should navigate to the 'Share' fragment`() { - val navDirectionsSlot = slot() - every { navController.navigate(capture(navDirectionsSlot), null) } just Runs - controller.handleBookmarkSharing(item) verify { - navController.navigate(navDirectionsSlot.captured, null) + navController.navigate(withArg { + assertEquals(R.id.action_global_shareFragment, it.actionId) + }, null) } } @@ -313,8 +312,7 @@ class BookmarkControllerTest { @Test fun `handleRequestSync dispatches actions in the correct order`() { - every { homeActivity.components.backgroundServices.accountManager } returns mockk(relaxed = true) - coEvery { homeActivity.bookmarkStorage.getBookmark(any()) } returns tree + coEvery { bookmarkStorage.getBookmark(any()) } returns tree coEvery { loadBookmarkNode.invoke(any()) } returns tree controller.handleRequestSync() diff --git a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt index 9950dd5b6..a66336b71 100644 --- a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt @@ -83,11 +83,13 @@ class ShareControllerTest { @Test fun `handleShareToApp should start a new sharing activity and close this`() = runBlocking { + val appPackageName = "package" + val appClassName = "activity" val appShareOption = AppShareOption( name = "app", icon = mockk(), - packageName = "package", - activityName = "activity" + packageName = appPackageName, + activityName = appClassName ) val shareIntent = slot() // Our share Intent uses `FLAG_ACTIVITY_NEW_TASK` but when resolving the startActivity call @@ -108,8 +110,8 @@ class ShareControllerTest { assertEquals(textToShare, shareIntent.captured.extras!![Intent.EXTRA_TEXT]) assertEquals("text/plain", shareIntent.captured.type) assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, shareIntent.captured.flags) - assertEquals("package", shareIntent.captured.component!!.packageName) - assertEquals("activity", shareIntent.captured.component!!.className) + assertEquals(appPackageName, shareIntent.captured.component!!.packageName) + assertEquals(appClassName, shareIntent.captured.component!!.className) verify { recentAppStorage.updateRecentApp(appShareOption.activityName) } verifyOrder { From 863b1357231c079387fb3b0fc1a6fdc57cb63bc0 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Tue, 29 Sep 2020 00:15:29 +0000 Subject: [PATCH 20/30] Import l10n. --- app/src/main/res/values-be/strings.xml | 16 ++++++++ app/src/main/res/values-el/strings.xml | 49 +++++++++++++++++++++++++ app/src/main/res/values-ka/strings.xml | 15 ++++++-- app/src/main/res/values-kab/strings.xml | 28 ++++++++++++++ app/src/main/res/values-rm/strings.xml | 40 +++++++++++++++----- 5 files changed, 135 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 64875a114..cb9a9128d 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -142,6 +142,8 @@ Усталяваць Сінхранізаваныя карткі + + Сінхранізаваць ізноў Знайсці на старонцы @@ -264,6 +266,8 @@ Адкрываць спасылкі ў прыватнай картцы Дазволіць здымкі экрана ў прыватным рэжыме + + Калі гэта дазволена, прыватныя карткі таксама будуць бачныя, калі адкрыта некалькі праграм Дадаць ярлык прыватнага аглядання @@ -460,6 +464,11 @@ Пацягніце, каб абнавіць + + Пракруціце, каб схаваць панэль інструментаў + + Пасуньце ўбок панэль інструментаў, каб пераключыць карткі + Сеансы @@ -1278,6 +1287,9 @@ Назва ярлыка + + Вы можаце лёгка дадаць гэты вэб-сайт на хатні экран вашай прылады, каб мець да яго імгненны доступ і аглядаць хутчэй, нібыта гэта асобная праграма. + Лагіны і паролі @@ -1348,8 +1360,12 @@ Сайт скапіяваны ў буфер абмену Капіяваць пароль + + Ачысціць пароль Капіяваць імя карыстальніка + + Ачысціць імя карыстальніка Капіяваць сайт diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 5312f601a..99de91e5d 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -85,11 +85,15 @@ Απόρριψη + + Απαιτείται πρόσβαση στην κάμερα. Μεταβείτε στις Ρυθμίσεις Android, πατήστε "Δικαιώματα" και πατήστε "Να επιτρέπεται". Μετάβαση στις ρυθμίσεις Απόρριψη + + Ρυθμίστε τις ανοιχτές καρτέλες ώστε να κλείνουν αυτόματα αυτές που δεν έχουν προβληθεί την προηγούμενη ημέρα, εβδομάδα ή μήνα. Εμφάνιση επιλογών @@ -265,6 +269,8 @@ Άνοιγμα συνδέσμων σε ιδιωτική καρτέλα Να επιτρέπονται στιγμιότυπα στην ιδιωτική περιήγηση + + Αν επιτρέπεται, οι ιδιωτικές καρτέλες θα είναι επίσης ορατές όταν είναι ανοιχτές πολλές εφαρμογές Προσθήκη συντόμευσης ιδιωτικής περιήγησης @@ -398,16 +404,25 @@ Η προστασία από καταγραφή είναι ανενεργή για αυτές τις ιστοσελίδες Ενεργοποίηση για όλες τις σελίδες + + Οι εξαιρέσεις σας επιτρέπουν να απενεργοποιήσετε την προστασία από καταγραφή σε συγκεκριμένες σελίδες. Μάθετε περισσότερα + + Γενικά ανενεργή, μεταβείτε στις Ρυθμίσεις για ενεργοποίηση. + Τηλεμετρία Δεδομένα χρήσης και τεχνικά δεδομένα + + Αποστέλλει πληροφορίες επιδόσεων, χρήσης, υλικού συσκευής και εξατομίκευσης του προγράμματος περιήγησης στη Mozilla για βελτίωση του %1$s Δεδομένα μάρκετινγκ + + Αποστέλλει δεδομένα σχετικά με τις λειτουργίες που χρησιμοποιείτε στο %1$s με το Leanplum, την εταιρεία μάρκετινγκ για κινητές συσκευές. Πειράματα @@ -457,6 +472,16 @@ Χρήση θέματος συσκευής + + + Τράβηγμα για ανανέωση + + Κύλιση για απόκρυψη γραμμής εργαλείων + + Ολίσθηση γραμμής εργαλείων προς τα πλάγια για εναλλαγή καρτελών + + Ολίσθηση γραμμής εργαλείων προς τα πάνω για άνοιγμα καρτελών + Συνεδρίες @@ -1076,10 +1101,14 @@ Οι ρυθμίσεις απορρήτου και ασφάλειας αποκλείουν ιχνηλάτες, κακόβουλο λογισμικό και εταιρείες που σας ακολουθούν. Τυπική (προεπιλογή) + + Φραγή λιγότερων ιχνηλατών. Οι σελίδες θα φορτώνονται κανονικά. Αυστηρή (προτείνεται) Αυστηρή + + Φραγή περισσότερων ιχνηλατών, διαφημίσεων και αναδυόμενων παραθύρων. Οι σελίδες φορτώνονται ταχύτερα, αλλά ορισμένα χαρακτηριστικά ενδέχεται να μην λειτουργούν. @@ -1113,6 +1142,8 @@ Επιλογή θέματος + + Εξοικονόμηση μπαταρίας και προστασία ματιών με τη σκοτεινή λειτουργία. Αυτόματο @@ -1216,12 +1247,20 @@ Περιορίζει την ικανότητα των κοινωνικών δικτύων να παρακολουθούν τη δραστηριότητά σας στο διαδίκτυο. Cookies ιχνηλάτησης μεταξύ ιστοσελίδων + + Αποκλείει τα cookies που χρησιμοποιούν τα δίκτυα διαφημίσεων και οι εταιρείες ανάλυσης δεδομένων για τη συλλογή δεδομένων περιήγησής σας από πολλαπλές ιστοσελίδες. Cryptominers + + Εμποδίζει τα κακόβουλα σενάρια από το να προσπελάσουν τη συσκευή σας για εξόρυξη ψηφιακού νομίσματος. Fingerprinters + + Διακόπτει τη συλλογή μοναδικών, αναγνωριστικών δεδομένων της συσκευής σας που μπορούν να χρησιμοποιηθούν για σκοπούς καταγραφής. Περιεχόμενο καταγραφής + + Διακόπτει τη φόρτωση εξωτερικών διαφημίσεων, βίντεο και άλλου περιεχομένου που περιέχει κώδικα καταγραφής. Ίσως επηρεάσει τη λειτουργικότητα μερικών ιστοσελίδων. Κάθε φορά που η ασπίδα είναι μωβ, το %s έχει αποκλείσει ιχνηλάτες σε μια σελίδα. Πατήστε για περισσότερες πληροφορίες. @@ -1247,6 +1286,9 @@ Ανακατεύθυνση ιχνηλατών + + Διαγράφει τα cookies που τοποθετούνται από ανακατευθύνσεις σε γνωστές ιστοσελίδες καταγραφής. + Υποστήριξη @@ -1294,6 +1336,9 @@ Όνομα συντόμευσης + + Μπορείτε εύκολα να προσθέσετε αυτή την ιστοσελίδα στην αρχική οθόνη για άμεση πρόσβαση και ταχύτερη περιήγηση, σαν να ήταν εφαρμογή. + Συνδέσεις και κωδικοί πρόσβασης @@ -1378,6 +1423,10 @@ Απόκρυψη κωδικού πρόσβασης Ξεκλειδώστε για να δείτε τις αποθηκευμένες συνδέσεις σας + + Προστασία στοιχείων σύνδεσης + + Ορίστε ένα μοτίβο κλειδώματος συσκευής, ένα ΡΙΝ ή έναν κωδικό πρόσβασης για προστασία των αποθηκευμένων στοιχείων σύνδεσης, σε περίπτωση που κάποιος τρίτος αποκτήσει πρόσβαση στη συσκευή σας. Αργότερα diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index d74dc6441..05503280a 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -311,7 +311,7 @@ შემმუშავებლის ხელსაწყოები - დაშორებული გამართვა USB-ით + დაშორებული USB-გამართვა საძიებო სისტემების ჩვენება @@ -336,7 +336,7 @@ ბმულების გახსნა პროგრამებში - ჩამოტვირთვის გარეშე მმართველი + გარეშე მმართველი ჩამოტვირთვების დამატებები @@ -992,7 +992,7 @@ გამოხვალთ ანგარიშებიდან უმეტეს საიტზე - შენახული სურათებისა და ფაილების ასლები + სურათები და ფაილების კეში გაათავისუფლებს მეხსიერებას @@ -1163,6 +1163,8 @@ შედით კამერის დახმარებით ელფოსტის გამოყენება სანაცვლოდ + + შექმენით და დაასინქრონეთ Firefox მოწყობილობებზე.]]> Firefox შეწყვეტს ამ ანგარიშის სინქრონიზაციას, მაგრამ ამ მოწყობილობიდან დათვალიერების მონაცემები არ წაიშლება. @@ -1172,7 +1174,7 @@ გაუქმება - ნაგულისხმევი საქაღალდეების ჩასწორება ვერ ხერხდება + ნაგულისხმევი საქაღალდეების ჩასწორება ვერ მოხერხდება @@ -1273,6 +1275,11 @@ The first parameter is the app name --> %s | OSS-ბიბლიოთეკები + + გადამმისამართებელი მეთვალყურეები + + ასუფთავებს გადამისამართებით მიღებულ ფუნთუშებს, ცნობილი მეთვალყურეებისგან. + მხარდაჭერა diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 05fe820ab..960565122 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -294,6 +294,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Asentel Asebter agejdan + + Isillifen Sagen @@ -475,6 +477,17 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ḍfer asntel n ibnek + + + Zuɣer i usesfer + + Drurem i wakken ad teffreḍ agalis n yifecka + + + Err agalis n yifecka deg tama i ubeddel n waccaren + + Err agalis n yifecka deg usawen i twaledyawt n waccaren + Tiɣimiyen @@ -1065,6 +1078,10 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Bdu amtawi n ticraḍ, awalen uffiren, akked waṭas-nniḍen s umiḍan n Firefox. Issin ugar + + Aql-ak·akem teqqneḍ d %s ɣef yiminig-nniḍen n Firefox s yibenk-a. Tebɣiḍ ad teqqneḍ s umiḍan-a? Ih, qqen-iyi @@ -1156,6 +1173,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Qqen s tkamirat-ik Seqdec tansa n yimayl deg umḍiq-is + + Rnu yiwen i umtaw n Firefox gar yibenkan.]]> Firefox ad iseḥbes amtawi d umiḍan-inek, acukan ur ittekkes ara isefka-inek n tunigin seg uselkim-a. @@ -1266,6 +1285,12 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara The first parameter is the app name --> %s | timkerḍiyin OSS + + Welleh i yineḍfaren + + + Yesfaḍay inagan n tuqqna yettusbadun s yiwellhen n yismal web n uḍfar yettwassnen. + Tallelt @@ -1310,6 +1335,9 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Isem n unegzum + + Tzemreḍ s sshal ad ternuḍ asmel-a web ɣer ugdil agejdan n yibenk-inek·inem i wakken ad tesɛuḍ anekcum askudan d tunigin taruradt s termt i icuban asnas. + Inekcam d wawalen uffiren diff --git a/app/src/main/res/values-rm/strings.xml b/app/src/main/res/values-rm/strings.xml index 8ab8eab75..e9b9e06a2 100644 --- a/app/src/main/res/values-rm/strings.xml +++ b/app/src/main/res/values-rm/strings.xml @@ -142,6 +142,8 @@ Installar Tabs sincronisads + + Resincronisar Tschertgar en la pagina @@ -263,6 +265,8 @@ Avrir colliaziuns en in tab privat Permetter maletgs dal visur en il modus privat + + Sche permess, èn tabs privats era visibels en cas che pliras apps èn avertas Agiuntar ina scursanida al modus privat @@ -283,6 +287,8 @@ Design Pagina iniziala + + Gests Persunalisar @@ -318,8 +324,12 @@ Tschertgar en la cronologia da navigaziun Tschertgar en ils segnapaginas + + Tschertgar en ils tabs sincronisads Parameters dal conto + + Cumplettar automaticamain URLs Avrir colliaziuns en apps @@ -456,6 +466,16 @@ Tenor il design tschernì sin l\'apparat + + + Trair per actualisar + + Scrollar per zuppentar la trav d\'utensils + + Stritgar da la vart la trav d\'utensils per midar tab + + Stritgar ensi la trav d\'utensils per avrir tabs + Sesidas @@ -1045,7 +1065,7 @@ - Ti es annunzià sco %s en in auter navigatur da Firefox sin quest telefonin. Vuls ti t\'annunziar cun quest conto? + Ti es annunzià sco %s en in auter navigatur da Firefox sin quest apparat. Vuls ti t\'annunziar cun quest conto? Gea, m\'annunziar @@ -1137,6 +1157,8 @@ T\'annunzia cun tia camera Utilisar l\'e-mail + + Creescha in per sincronisar Firefox tranter differents apparats.]]> Firefox chala da sincronisar cun tes conto, ma las datas da navigaziun restan sin quest apparat. @@ -1295,9 +1317,6 @@ Num da la scursanida - - Ti pos agiuntar a moda simpla questa website al visur da partenza da tes telefonin per avair access direct e navigar pli svelt, sco sch\'i fiss ina app. - Infurmaziuns d\'annunzia e pleds-clav @@ -1526,7 +1545,7 @@ Datas d\'annunzia cun quest num d\'utilisader existan gia - + Colliar in auter apparat. Re-autentifitgescha per plaschair. @@ -1546,8 +1565,6 @@ Cuntanschì la limita da paginas preferidas - - Per agiuntar ina nova pagina preferida stos ti l\'emprim allontanar in\'autra. Smatga ditg sin ina pagina e tscherna «Allontanar». OK, chapì @@ -1557,7 +1574,7 @@ Allontanar - Rabla ora il maximum da %s. @@ -1565,4 +1582,9 @@ Rimna las chaussas che t\'èn impurtantas Gruppescha retschertgas, paginas e tabs sumegliants per pli tard pudair acceder pli svelt. - + + Ti es annunzià sco %s en in auter navigatur da Firefox sin quest telefonin. Vuls ti t\'annunziar cun quest conto? + + Ti pos agiuntar a moda simpla questa website al visur da partenza da tes telefonin per avair access direct e navigar pli svelt, sco sch\'i fiss ina app. + + From 01fdb4ac3fa545ea8689517b2c7c61b26d92ee81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?So=CC=88ren=20Hentzschel?= Date: Sat, 29 Aug 2020 15:13:44 +0200 Subject: [PATCH 21/30] For #11561 - changed inactive heading and menu icon color in dark mode --- app/src/main/res/layout/component_tabstray.xml | 2 +- app/src/main/res/values-night/colors.xml | 1 + app/src/main/res/values/colors.xml | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/component_tabstray.xml b/app/src/main/res/layout/component_tabstray.xml index 0005ecb5a..baeb90297 100644 --- a/app/src/main/res/layout/component_tabstray.xml +++ b/app/src/main/res/layout/component_tabstray.xml @@ -159,7 +159,7 @@ android:background="?android:attr/selectableItemBackgroundBorderless" android:contentDescription="@string/open_tabs_menu" android:visibility="visible" - app:tint="@color/accent_normal_theme" + app:tint="@color/tab_tray_heading_icon_menu_normal_theme" app:layout_constraintBottom_toBottomOf="@id/tab_layout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/tab_layout" diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 5f0f08d11..0190bf370 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -65,6 +65,7 @@ @color/tab_tray_item_media_background_dark_theme @color/tab_tray_heading_icon_dark_theme @color/tab_tray_heading_icon_inactive_dark_theme + @color/tab_tray_heading_icon_menu_dark_theme @color/tab_tray_item_thumbnail_background_dark_theme @color/tab_tray_item_thumbnail_icon_dark_theme @color/tab_tray_selected_mask_dark_theme diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index fb96fd10e..89963e642 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -75,6 +75,7 @@ #312A65 @color/ink_20 @color/ink_20_48a + @color/accent_normal_theme @color/photonLightGrey10 @color/photonLightGrey60 @color/violet_70_12a @@ -141,7 +142,8 @@ @color/photonDarkGrey10 #9059FF @color/violet_50 - @color/violet_50_48a + @color/photonLightGrey05 + @color/photonLightGrey05 @color/photonDarkGrey50 @color/photonDarkGrey05 @color/violet_50_32a @@ -264,6 +266,7 @@ @color/tab_tray_item_media_background_light_theme @color/tab_tray_heading_icon_light_theme @color/tab_tray_heading_icon_inactive_light_theme + @color/tab_tray_heading_icon_menu_light_theme @color/tab_tray_item_thumbnail_background_light_theme @color/tab_tray_item_thumbnail_icon_light_theme @color/tab_tray_selected_mask_light_theme From f2a6aa4f25f8e5dc358b59238185916cde438368 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Mon, 17 Aug 2020 12:45:46 -0700 Subject: [PATCH 22/30] Add tests for initial collection creation state --- .../collections/CollectionCreationFragment.kt | 57 +------ .../collections/CollectionCreationStore.kt | 63 +++++++ .../CollectionCreationFragmentTest.kt | 65 -------- .../CollectionCreationStoreTest.kt | 154 ++++++++++++++++++ 4 files changed, 224 insertions(+), 115 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt index 4e15537f7..0a8853caf 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt @@ -9,7 +9,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.VisibleForTesting import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.navArgs @@ -22,10 +21,7 @@ import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.state.ext.consumeFrom import org.mozilla.fenix.R import org.mozilla.fenix.components.StoreProvider -import org.mozilla.fenix.ext.getMediaStateForSession import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.toShortUrl -import org.mozilla.fenix.home.Tab @ExperimentalCoroutinesApi class CollectionCreationFragment : DialogFragment() { @@ -47,27 +43,16 @@ class CollectionCreationFragment : DialogFragment() { val view = inflater.inflate(R.layout.fragment_create_collection, container, false) val args: CollectionCreationFragmentArgs by navArgs() - val store = requireComponents.core.store - val publicSuffixList = requireComponents.publicSuffixList - val tabs = store.state.getTabs(args.tabIds, publicSuffixList) - val selectedTabs = if (args.selectedTabIds != null) { - store.state.getTabs(args.selectedTabIds, publicSuffixList).toSet() - } else { - if (tabs.size == 1) setOf(tabs.first()) else emptySet() - } - - val tabCollections = requireComponents.core.tabCollectionStorage.cachedTabCollections - val selectedTabCollection = args.selectedTabCollectionId - .let { id -> tabCollections.firstOrNull { it.id == id } } - collectionCreationStore = StoreProvider.get(this) { CollectionCreationStore( - CollectionCreationState( - tabs = tabs, - selectedTabs = selectedTabs, + createInitialCollectionCreationState( + browserState = requireComponents.core.store.state, + tabCollectionStorage = requireComponents.core.tabCollectionStorage, + publicSuffixList = requireComponents.publicSuffixList, saveCollectionStep = args.saveCollectionStep, - tabCollections = tabCollections, - selectedTabCollection = selectedTabCollection + tabIds = args.tabIds, + selectedTabIds = args.selectedTabIds, + selectedTabCollectionId = args.selectedTabCollectionId ) ) } @@ -110,31 +95,3 @@ class CollectionCreationFragment : DialogFragment() { return dialog } } - -@VisibleForTesting -internal fun BrowserState.getTabs( - tabIds: Array?, - publicSuffixList: PublicSuffixList -): List { - return tabIds - ?.mapNotNull { id -> findTab(id) } - ?.map { it.toTab(this, publicSuffixList) } - .orEmpty() -} - -private fun TabSessionState.toTab( - state: BrowserState, - publicSuffixList: PublicSuffixList, - selected: Boolean? = null -): Tab { - val url = readerState.activeUrl ?: content.url - return Tab( - sessionId = this.id, - url = url, - hostname = url.toShortUrl(publicSuffixList), - title = content.title, - selected = selected, - icon = content.icon, - mediaState = state.getMediaStateForSession(this.id) - ) -} diff --git a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt index f15ccf963..750ab0090 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt @@ -4,11 +4,19 @@ package org.mozilla.fenix.collections +import androidx.annotation.VisibleForTesting +import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.TabSessionState import mozilla.components.feature.tab.collections.TabCollection +import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store import org.mozilla.fenix.collections.CollectionCreationAction.StepChanged +import org.mozilla.fenix.components.TabCollectionStorage +import org.mozilla.fenix.ext.getMediaStateForSession +import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.home.Tab class CollectionCreationStore( @@ -43,6 +51,61 @@ data class CollectionCreationState( val defaultCollectionNumber: Int = 1 ) : State +fun createInitialCollectionCreationState( + browserState: BrowserState, + tabCollectionStorage: TabCollectionStorage, + publicSuffixList: PublicSuffixList, + saveCollectionStep: SaveCollectionStep, + tabIds: Array?, + selectedTabIds: Array?, + selectedTabCollectionId: Long +) : CollectionCreationState { + val tabs = browserState.getTabs(tabIds, publicSuffixList) + val selectedTabs = if (selectedTabIds != null) { + browserState.getTabs(selectedTabIds, publicSuffixList).toSet() + } else { + if (tabs.size == 1) setOf(tabs.first()) else emptySet() + } + + val tabCollections = tabCollectionStorage.cachedTabCollections + val selectedTabCollection = tabCollections.firstOrNull { it.id == selectedTabCollectionId } + + return CollectionCreationState( + tabs = tabs, + selectedTabs = selectedTabs, + saveCollectionStep = saveCollectionStep, + tabCollections = tabCollections, + selectedTabCollection = selectedTabCollection + ) +} + +@VisibleForTesting +internal fun BrowserState.getTabs( + tabIds: Array?, + publicSuffixList: PublicSuffixList +): List { + return tabIds + ?.mapNotNull { id -> findTab(id) } + ?.map { it.toTab(this, publicSuffixList) } + .orEmpty() +} + +private fun TabSessionState.toTab( + state: BrowserState, + publicSuffixList: PublicSuffixList +): Tab { + val url = readerState.activeUrl ?: content.url + return Tab( + sessionId = this.id, + url = url, + hostname = url.toShortUrl(publicSuffixList), + title = content.title, + selected = null, + icon = content.icon, + mediaState = state.getMediaStateForSession(this.id) + ) +} + sealed class CollectionCreationAction : Action { object AddAllTabs : CollectionCreationAction() object RemoveAllTabs : CollectionCreationAction() diff --git a/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationFragmentTest.kt index 913590f0d..bd04dc4ef 100644 --- a/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationFragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationFragmentTest.kt @@ -10,13 +10,10 @@ import io.mockk.impl.annotations.MockK import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.state.state.BrowserState -import mozilla.components.browser.state.state.ReaderState import mozilla.components.browser.state.state.createTab -import mozilla.components.feature.tab.collections.Tab import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.test.robolectric.createAddedTestFragment import mozilla.components.support.test.robolectric.testContext -import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -31,9 +28,6 @@ private const val SESSION_ID_MOZILLA = "0" private const val URL_BCC = "www.bcc.co.uk" private const val SESSION_ID_BCC = "1" -private const val SESSION_ID_BAD_1 = "not a real session id" -private const val SESSION_ID_BAD_2 = "definitely not a real session id" - @ExperimentalCoroutinesApi @RunWith(FenixRobolectricTestRunner::class) class CollectionCreationFragmentTest { @@ -71,63 +65,4 @@ class CollectionCreationFragmentTest { fragment.dismiss() assertNull(fragment.dialog) } - - @Test - fun `GIVEN tabs are present in state WHEN getTabs is called THEN tabs will be returned`() { - val tabs = state.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), publicSuffixList) - - val hosts = tabs.map { it.hostname } - - assertEquals(URL_MOZILLA, hosts[0]) - assertEquals(URL_BCC, hosts[1]) - } - - @Test - fun `GIVEN some tabs are present in state WHEN getTabs is called THEN only valid tabs will be returned`() { - val tabs = state.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BAD_1), publicSuffixList) - - val hosts = tabs.map { it.hostname } - - assertEquals(URL_MOZILLA, hosts[0]) - assertEquals(1, hosts.size) - } - - @Test - fun `GIVEN tabs are not present in state WHEN getTabs is called THEN an empty list will be returned`() { - val tabs = state.getTabs(arrayOf(SESSION_ID_BAD_1, SESSION_ID_BAD_2), publicSuffixList) - - assertEquals(emptyList(), tabs) - } - - @Test - fun `WHEN getTabs is called will null tabIds THEN an empty list will be returned`() { - val tabs = state.getTabs(null, publicSuffixList) - - assertEquals(emptyList(), tabs) - } - - @Test - fun `toTab uses active reader URL`() { - val tabWithoutReaderState = createTab(url = "https://example.com", id = "1") - - val tabWithInactiveReaderState = createTab(url = "https://blog.mozilla.org", id = "2", - readerState = ReaderState(active = false, activeUrl = null) - ) - - val tabWithActiveReaderState = createTab(url = "moz-extension://123", id = "3", - readerState = ReaderState(active = true, activeUrl = "https://blog.mozilla.org/123") - ) - - val state = BrowserState( - tabs = listOf(tabWithoutReaderState, tabWithInactiveReaderState, tabWithActiveReaderState) - ) - val tabs = state.getTabs( - arrayOf(tabWithoutReaderState.id, tabWithInactiveReaderState.id, tabWithActiveReaderState.id), - publicSuffixList - ) - - assertEquals(tabWithoutReaderState.content.url, tabs[0].url) - assertEquals(tabWithInactiveReaderState.content.url, tabs[1].url) - assertEquals("https://blog.mozilla.org/123", tabs[2].url) - } } diff --git a/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationStoreTest.kt b/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationStoreTest.kt index e6d42072d..3415bc4ca 100644 --- a/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationStoreTest.kt @@ -4,17 +4,54 @@ package org.mozilla.fenix.collections +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.mockk +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.ReaderState +import mozilla.components.browser.state.state.createTab +import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.test.ext.joinBlocking import org.junit.Assert.assertEquals +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.home.Tab +private const val URL_MOZILLA = "www.mozilla.org" +private const val SESSION_ID_MOZILLA = "0" +private const val URL_BCC = "www.bcc.co.uk" +private const val SESSION_ID_BCC = "1" + +private const val SESSION_ID_BAD_1 = "not a real session id" +private const val SESSION_ID_BAD_2 = "definitely not a real session id" + +@ExperimentalCoroutinesApi @RunWith(FenixRobolectricTestRunner::class) class CollectionCreationStoreTest { + @MockK private lateinit var tabCollectionStorage: TabCollectionStorage + @MockK(relaxed = true) private lateinit var publicSuffixList: PublicSuffixList + + private val sessionMozilla = createTab(URL_MOZILLA, id = SESSION_ID_MOZILLA) + private val sessionBcc = createTab(URL_BCC, id = SESSION_ID_BCC) + private val state = BrowserState( + tabs = listOf(sessionMozilla, sessionBcc) + ) + + @Before + fun before() { + MockKAnnotations.init(this) + every { tabCollectionStorage.cachedTabCollections } returns emptyList() + every { publicSuffixList.stripPublicSuffix(URL_MOZILLA) } returns CompletableDeferred(URL_MOZILLA) + every { publicSuffixList.stripPublicSuffix(URL_BCC) } returns CompletableDeferred(URL_BCC) + } + @Test fun `select and deselect all tabs`() { val tabs = listOf(mockk(), mockk()) @@ -73,4 +110,121 @@ class CollectionCreationStoreTest { assertEquals(SaveCollectionStep.RenameCollection, store.state.saveCollectionStep) assertEquals(3, store.state.defaultCollectionNumber) } + + @Test + fun `GIVEN no selected tab ids WHEN create initial state THEN only tab will be selected`() { + val result = createInitialCollectionCreationState( + browserState = state, + tabCollectionStorage = tabCollectionStorage, + publicSuffixList = publicSuffixList, + saveCollectionStep = SaveCollectionStep.NameCollection, + tabIds = arrayOf(SESSION_ID_MOZILLA), + selectedTabIds = null, + selectedTabCollectionId = 0 + ) + + assertEquals(SaveCollectionStep.NameCollection, result.saveCollectionStep) + assertEquals(1, result.tabs.size) + assertEquals(SESSION_ID_MOZILLA, result.tabs[0].sessionId) + assertEquals(1, result.selectedTabs.size) + assertEquals(SESSION_ID_MOZILLA, result.selectedTabs.first().sessionId) + } + + @Test + fun `GIVEN no selected tab ids WHEN create initial state with many tabs THEN nothing will be selected`() { + val result = createInitialCollectionCreationState( + browserState = state, + tabCollectionStorage = tabCollectionStorage, + publicSuffixList = publicSuffixList, + saveCollectionStep = SaveCollectionStep.NameCollection, + tabIds = arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), + selectedTabIds = null, + selectedTabCollectionId = 0 + ) + + assertEquals(SaveCollectionStep.NameCollection, result.saveCollectionStep) + assertEquals(2, result.tabs.size) + assertEquals(SESSION_ID_MOZILLA, result.tabs[0].sessionId) + assertEquals(SESSION_ID_BCC, result.tabs[1].sessionId) + assertEquals(0, result.selectedTabs.size) + } + + @Test + fun `GIVEN selected tab ids WHEN create initial state THEN select tabs`() { + val result = createInitialCollectionCreationState( + browserState = state, + tabCollectionStorage = tabCollectionStorage, + publicSuffixList = publicSuffixList, + saveCollectionStep = SaveCollectionStep.RenameCollection, + tabIds = arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), + selectedTabIds = arrayOf(SESSION_ID_BCC), + selectedTabCollectionId = 0 + ) + + assertEquals(SaveCollectionStep.RenameCollection, result.saveCollectionStep) + assertEquals(2, result.tabs.size) + assertEquals(SESSION_ID_MOZILLA, result.tabs[0].sessionId) + assertEquals(SESSION_ID_BCC, result.tabs[1].sessionId) + assertEquals(1, result.selectedTabs.size) + assertEquals(SESSION_ID_BCC, result.selectedTabs.first().sessionId) + } + + @Test + fun `GIVEN tabs are present in state WHEN getTabs is called THEN tabs will be returned`() { + val tabs = state.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), publicSuffixList) + + val hosts = tabs.map { it.hostname } + + assertEquals(URL_MOZILLA, hosts[0]) + assertEquals(URL_BCC, hosts[1]) + } + + @Test + fun `GIVEN some tabs are present in state WHEN getTabs is called THEN only valid tabs will be returned`() { + val tabs = state.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BAD_1), publicSuffixList) + + val hosts = tabs.map { it.hostname } + + assertEquals(URL_MOZILLA, hosts[0]) + assertEquals(1, hosts.size) + } + + @Test + fun `GIVEN tabs are not present in state WHEN getTabs is called THEN an empty list will be returned`() { + val tabs = state.getTabs(arrayOf(SESSION_ID_BAD_1, SESSION_ID_BAD_2), publicSuffixList) + + assertEquals(emptyList(), tabs) + } + + @Test + fun `WHEN getTabs is called will null tabIds THEN an empty list will be returned`() { + val tabs = state.getTabs(null, publicSuffixList) + + assertEquals(emptyList(), tabs) + } + + @Test + fun `toTab uses active reader URL`() { + val tabWithoutReaderState = createTab(url = "https://example.com", id = "1") + + val tabWithInactiveReaderState = createTab(url = "https://blog.mozilla.org", id = "2", + readerState = ReaderState(active = false, activeUrl = null) + ) + + val tabWithActiveReaderState = createTab(url = "moz-extension://123", id = "3", + readerState = ReaderState(active = true, activeUrl = "https://blog.mozilla.org/123") + ) + + val state = BrowserState( + tabs = listOf(tabWithoutReaderState, tabWithInactiveReaderState, tabWithActiveReaderState) + ) + val tabs = state.getTabs( + arrayOf(tabWithoutReaderState.id, tabWithInactiveReaderState.id, tabWithActiveReaderState.id), + publicSuffixList + ) + + assertEquals(tabWithoutReaderState.content.url, tabs[0].url) + assertEquals(tabWithInactiveReaderState.content.url, tabs[1].url) + assertEquals("https://blog.mozilla.org/123", tabs[2].url) + } } From acbad66f4569236d5544237771d6249929c5720d Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Mon, 17 Aug 2020 12:52:12 -0700 Subject: [PATCH 23/30] Add test for initial logins list state --- .../settings/logins/LoginsFragmentStore.kt | 11 ++++++++++ .../logins/fragment/EditLoginFragment.kt | 14 +++--------- .../logins/fragment/LoginDetailFragment.kt | 11 ++-------- .../logins/fragment/SavedLoginsFragment.kt | 12 ++-------- .../logins/LoginsFragmentStoreTest.kt | 22 +++++++++++++++++++ 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt index ea130f07e..9ab138c30 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt @@ -10,6 +10,7 @@ import mozilla.components.concept.storage.Login import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store +import org.mozilla.fenix.utils.Settings /** * Class representing a parcelable saved logins item @@ -80,6 +81,16 @@ data class LoginsListState( val duplicateLogins: List ) : State +fun createInitialLoginsListState(settings: Settings) = LoginsListState( + isLoading = true, + loginList = emptyList(), + filteredItems = emptyList(), + searchedForText = null, + sortingStrategy = settings.savedLoginsSortingStrategy, + highlightedItem = settings.savedLoginsMenuHighlightedItem, + duplicateLogins = emptyList() // assume on load there are no dupes +) + /** * Handles changes in the saved logins list, including updates and filtering. */ diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt index c65654fe0..62a624169 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt @@ -33,11 +33,11 @@ import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.settings.logins.LoginsAction import org.mozilla.fenix.settings.logins.LoginsFragmentStore -import org.mozilla.fenix.settings.logins.LoginsListState import org.mozilla.fenix.settings.logins.SavedLogin -import org.mozilla.fenix.settings.logins.togglePasswordReveal import org.mozilla.fenix.settings.logins.controller.SavedLoginsStorageController +import org.mozilla.fenix.settings.logins.createInitialLoginsListState import org.mozilla.fenix.settings.logins.interactor.EditLoginInteractor +import org.mozilla.fenix.settings.logins.togglePasswordReveal /** * Displays the editable saved login information for a single website @@ -69,15 +69,7 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { loginsFragmentStore = StoreProvider.get(this) { LoginsFragmentStore( - LoginsListState( - isLoading = true, - loginList = listOf(), - filteredItems = listOf(), - searchedForText = null, - sortingStrategy = requireContext().settings().savedLoginsSortingStrategy, - highlightedItem = requireContext().settings().savedLoginsMenuHighlightedItem, - duplicateLogins = listOf() - ) + createInitialLoginsListState(requireContext().settings()) ) } diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt index ed452e33b..60c94290c 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt @@ -41,6 +41,7 @@ import org.mozilla.fenix.settings.logins.LoginsFragmentStore import org.mozilla.fenix.settings.logins.LoginsListState import org.mozilla.fenix.settings.logins.SavedLogin import org.mozilla.fenix.settings.logins.controller.SavedLoginsStorageController +import org.mozilla.fenix.settings.logins.createInitialLoginsListState import org.mozilla.fenix.settings.logins.interactor.LoginDetailInteractor import org.mozilla.fenix.settings.logins.togglePasswordReveal import org.mozilla.fenix.settings.logins.view.LoginDetailView @@ -68,15 +69,7 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) { val view = inflater.inflate(R.layout.fragment_login_detail, container, false) savedLoginsStore = StoreProvider.get(this) { LoginsFragmentStore( - LoginsListState( - isLoading = true, - loginList = listOf(), - filteredItems = listOf(), - searchedForText = null, - sortingStrategy = requireContext().settings().savedLoginsSortingStrategy, - highlightedItem = requireContext().settings().savedLoginsMenuHighlightedItem, - duplicateLogins = listOf() // assume on load there are no dupes - ) + createInitialLoginsListState(requireContext().settings()) ) } loginDetailView = LoginDetailView( diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsFragment.kt index fe2431ae0..dc50cc445 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsFragment.kt @@ -35,11 +35,11 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.logins.LoginsAction import org.mozilla.fenix.settings.logins.LoginsFragmentStore -import org.mozilla.fenix.settings.logins.LoginsListState import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu import org.mozilla.fenix.settings.logins.SortingStrategy import org.mozilla.fenix.settings.logins.controller.LoginsListController import org.mozilla.fenix.settings.logins.controller.SavedLoginsStorageController +import org.mozilla.fenix.settings.logins.createInitialLoginsListState import org.mozilla.fenix.settings.logins.interactor.SavedLoginsInteractor import org.mozilla.fenix.settings.logins.view.SavedLoginsListView @@ -77,15 +77,7 @@ class SavedLoginsFragment : Fragment() { val view = inflater.inflate(R.layout.fragment_saved_logins, container, false) savedLoginsStore = StoreProvider.get(this) { LoginsFragmentStore( - LoginsListState( - isLoading = true, - loginList = listOf(), - filteredItems = listOf(), - searchedForText = null, - sortingStrategy = requireContext().settings().savedLoginsSortingStrategy, - highlightedItem = requireContext().settings().savedLoginsMenuHighlightedItem, - duplicateLogins = listOf() // assume on load there are no dupes - ) + createInitialLoginsListState(requireContext().settings()) ) } diff --git a/app/src/test/java/org/mozilla/fenix/settings/logins/LoginsFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/settings/logins/LoginsFragmentStoreTest.kt index dbf41c2bd..3fe11eeb2 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/logins/LoginsFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/logins/LoginsFragmentStoreTest.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.settings.logins +import io.mockk.every import io.mockk.mockk import mozilla.components.concept.storage.Login import mozilla.components.support.test.ext.joinBlocking @@ -12,6 +13,7 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test +import org.mozilla.fenix.utils.Settings class LoginsFragmentStoreTest { @@ -34,6 +36,26 @@ class LoginsFragmentStoreTest { duplicateLogins = listOf() ) + @Test + fun `create initial state`() { + val settings = mockk() + every { settings.savedLoginsSortingStrategy } returns SortingStrategy.LastUsed + every { settings.savedLoginsMenuHighlightedItem } returns SavedLoginsSortingStrategyMenu.Item.LastUsedSort + + assertEquals( + LoginsListState( + isLoading = true, + loginList = emptyList(), + filteredItems = emptyList(), + searchedForText = null, + sortingStrategy = SortingStrategy.LastUsed, + highlightedItem = SavedLoginsSortingStrategyMenu.Item.LastUsedSort, + duplicateLogins = emptyList() + ), + createInitialLoginsListState(settings) + ) + } + @Test fun `convert login to saved login`() { val login = Login( From 25f62f1c76780b64a0324bf97488df5cd8a651e6 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Mon, 17 Aug 2020 12:58:03 -0700 Subject: [PATCH 24/30] Extract locale settings initial state --- .../collections/CollectionCreationFragment.kt | 4 ---- .../collections/CollectionCreationStore.kt | 3 ++- .../advanced/LocaleSettingsFragment.kt | 22 +++++-------------- .../settings/advanced/LocaleSettingsStore.kt | 12 ++++++++++ .../logins/fragment/LoginDetailFragment.kt | 1 - 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt index 0a8853caf..bb09b0d9c 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt @@ -14,10 +14,6 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.navArgs import kotlinx.android.synthetic.main.fragment_create_collection.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi -import mozilla.components.browser.state.selector.findTab -import mozilla.components.browser.state.state.BrowserState -import mozilla.components.browser.state.state.TabSessionState -import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.state.ext.consumeFrom import org.mozilla.fenix.R import org.mozilla.fenix.components.StoreProvider diff --git a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt index 750ab0090..c9fffedb8 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt @@ -51,6 +51,7 @@ data class CollectionCreationState( val defaultCollectionNumber: Int = 1 ) : State +@Suppress("LongParameterList") fun createInitialCollectionCreationState( browserState: BrowserState, tabCollectionStorage: TabCollectionStorage, @@ -59,7 +60,7 @@ fun createInitialCollectionCreationState( tabIds: Array?, selectedTabIds: Array?, selectedTabCollectionId: Long -) : CollectionCreationState { +): CollectionCreationState { val tabs = browserState.getTabs(tabIds, publicSuffixList) val selectedTabs = if (selectedTabIds != null) { browserState.getTabs(selectedTabIds, publicSuffixList).toSet() diff --git a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsFragment.kt index 2696115b1..4a307d29d 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsFragment.kt @@ -17,7 +17,6 @@ import kotlinx.android.synthetic.main.fragment_locale_settings.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.ktx.android.view.hideKeyboard -import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.R import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.ext.showToolbar @@ -40,7 +39,11 @@ class LocaleSettingsFragment : Fragment() { ): View? { val view = inflater.inflate(R.layout.fragment_locale_settings, container, false) - store = getStore() + store = StoreProvider.get(this) { + LocaleSettingsStore( + createInitialLocaleSettingsState(requireContext()) + ) + } interactor = LocaleSettingsInteractor( controller = DefaultLocaleSettingsController( activity = requireActivity(), @@ -88,19 +91,4 @@ class LocaleSettingsFragment : Fragment() { localeView.update(it) } } - - private fun getStore(): LocaleSettingsStore { - val supportedLocales = LocaleManager.getSupportedLocales() - val selectedLocale = LocaleManager.getSelectedLocale(requireContext()) - - return StoreProvider.get(this) { - LocaleSettingsStore( - LocaleSettingsState( - supportedLocales, - supportedLocales, - selectedLocale - ) - ) - } - } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsStore.kt b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsStore.kt index 98dc4ce66..59655c1b9 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsStore.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsStore.kt @@ -4,9 +4,11 @@ package org.mozilla.fenix.settings.advanced +import android.content.Context import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store +import mozilla.components.support.locale.LocaleManager import java.util.Locale class LocaleSettingsStore( @@ -27,6 +29,16 @@ data class LocaleSettingsState( val selectedLocale: Locale ) : State +fun createInitialLocaleSettingsState(context: Context): LocaleSettingsState { + val supportedLocales = LocaleManager.getSupportedLocales() + + return LocaleSettingsState( + supportedLocales, + supportedLocales, + selectedLocale = LocaleManager.getSelectedLocale(context) + ) +} + /** * Actions to dispatch through the `LocaleSettingsStore` to modify `LocaleSettingsState` through the reducer. */ diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt index 60c94290c..7120c0fe0 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt @@ -38,7 +38,6 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.simplifiedUrl import org.mozilla.fenix.settings.logins.LoginsFragmentStore -import org.mozilla.fenix.settings.logins.LoginsListState import org.mozilla.fenix.settings.logins.SavedLogin import org.mozilla.fenix.settings.logins.controller.SavedLoginsStorageController import org.mozilla.fenix.settings.logins.createInitialLoginsListState From b07af9ccd366bbe5696d69d3339dcadd448c5737 Mon Sep 17 00:00:00 2001 From: TrianguloY Date: Tue, 29 Sep 2020 05:43:44 +0200 Subject: [PATCH 25/30] For #13856 - Prevent overscroll in swipe to switch tabs gesture --- .../mozilla/fenix/browser/ToolbarGestureHandler.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt b/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt index 6c975a075..63d196f59 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt @@ -90,27 +90,27 @@ class ToolbarGestureHandler( when (getDestination()) { is Destination.Tab -> { // Restrict the range of motion for the views so you can't start a swipe in one direction - // then move your finger far enough in the other direction and make the content visually - // start sliding off screen the other way. + // then move your finger far enough or in the other direction and make the content visually + // start sliding off screen. tabPreview.translationX = when (gestureDirection) { GestureDirection.RIGHT_TO_LEFT -> min( windowWidth.toFloat() + previewOffset, tabPreview.translationX - distanceX - ) + ).coerceAtLeast(0f) GestureDirection.LEFT_TO_RIGHT -> max( -windowWidth.toFloat() - previewOffset, tabPreview.translationX - distanceX - ) + ).coerceAtMost(0f) } contentLayout.translationX = when (gestureDirection) { GestureDirection.RIGHT_TO_LEFT -> min( 0f, contentLayout.translationX - distanceX - ) + ).coerceAtLeast(-windowWidth.toFloat() - previewOffset) GestureDirection.LEFT_TO_RIGHT -> max( 0f, contentLayout.translationX - distanceX - ) + ).coerceAtMost(windowWidth.toFloat() + previewOffset) } } is Destination.None -> { From e49cd9c558cbd8b2a033b36e6db2a3d2dbada7fd Mon Sep 17 00:00:00 2001 From: ekager Date: Mon, 28 Sep 2020 10:19:05 -0700 Subject: [PATCH 26/30] For #15503 - Change recently closed max to 10 --- app/src/main/java/org/mozilla/fenix/components/Core.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 86fe8db4e..69033a78d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -377,6 +377,6 @@ class Core( private const val KEY_STRENGTH = 256 private const val KEY_STORAGE_NAME = "core_prefs" private const val PASSWORDS_KEY = "passwords" - private const val RECENTLY_CLOSED_MAX = 5 + private const val RECENTLY_CLOSED_MAX = 10 } } From 01e802fef079988f679916c94793cf891e455d51 Mon Sep 17 00:00:00 2001 From: ekager Date: Mon, 28 Sep 2020 12:05:27 -0700 Subject: [PATCH 27/30] For #15324 - Show tab settings and recently closed items in menu when no tabs --- .../mozilla/fenix/ui/TabbedBrowsingTest.kt | 4 ++-- .../org/mozilla/fenix/tabtray/TabTrayView.kt | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt index 02a270524..7ff893c41 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt @@ -262,11 +262,11 @@ class TabbedBrowsingTest { }.openTabTray { verifyNoTabsOpened() verifyNewTabButton() - verifyTabTrayOverflowMenu(false) + verifyTabTrayOverflowMenu(true) }.toggleToPrivateTabs { verifyNoTabsOpened() verifyNewTabButton() - verifyTabTrayOverflowMenu(false) + verifyTabTrayOverflowMenu(true) } } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index 2d24cc0ef..5ef4429f3 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -89,7 +89,8 @@ class TabTrayView( private var tabsTouchHelper: TabsTouchHelper private val collectionsButtonAdapter = SaveToCollectionsButtonAdapter(interactor, isPrivate) - private val syncedTabsController = SyncedTabsController(lifecycleOwner, view, store, concatAdapter) + private val syncedTabsController = + SyncedTabsController(lifecycleOwner, view, store, concatAdapter) private val syncedTabsFeature = ViewBoundFeatureWrapper() private var hasLoaded = false @@ -211,7 +212,10 @@ class TabTrayView( } tabTrayItemMenu = - TabTrayItemMenu(view.context, { view.tab_layout.selectedTabPosition == 0 }) { + TabTrayItemMenu( + view.context, + { tabs.isNotEmpty() && view.tab_layout.selectedTabPosition == 0 }, + { tabs.isNotEmpty() }) { when (it) { is TabTrayItemMenu.Item.ShareAllTabs -> interactor.onShareTabsClicked( isPrivateModeSelected @@ -431,7 +435,6 @@ class TabTrayView( } else { View.VISIBLE } - view.tab_tray_overflow.isVisible = !hasNoTabs counter_text.text = updateTabCounter(browserState.normalTabs.size) updateTabCounterContentDescription(browserState.normalTabs.size) @@ -592,9 +595,9 @@ class TabTrayView( // We add the offset, because the layoutManager is initialized with `reverseLayout`. // We also add 1 to display the tab item above the selected browser tab. val recyclerViewIndex = selectedBrowserTabIndex + - collectionsButtonAdapter.itemCount + - syncedTabsController.adapter.itemCount + - 1 + collectionsButtonAdapter.itemCount + + syncedTabsController.adapter.itemCount + + 1 layoutManager?.scrollToPosition(recyclerViewIndex) } @@ -614,6 +617,7 @@ class TabTrayView( class TabTrayItemMenu( private val context: Context, private val shouldShowSaveToCollection: () -> Boolean, + private val hasOpenTabs: () -> Boolean, private val onItemTapped: (Item) -> Unit = {} ) { @@ -643,7 +647,7 @@ class TabTrayItemMenu( ) { context.components.analytics.metrics.track(Event.TabsTrayShareAllTabsPressed) onItemTapped.invoke(Item.ShareAllTabs) - }, + }.apply { visible = hasOpenTabs }, SimpleBrowserMenuItem( context.getString(R.string.tab_tray_menu_tab_settings), @@ -665,7 +669,7 @@ class TabTrayItemMenu( ) { context.components.analytics.metrics.track(Event.TabsTrayCloseAllTabsPressed) onItemTapped.invoke(Item.CloseAllTabs) - } + }.apply { visible = hasOpenTabs } ) } } From cfbad1dae9a0fdcc2e5f10183cffa91b191427a5 Mon Sep 17 00:00:00 2001 From: Andrew Gaul Date: Thu, 24 Sep 2020 19:39:22 +0900 Subject: [PATCH 28/30] Show undo snackbars with padding with static bottom toolbar This specifically fixes the close tab snackbar. This commit mirrors the logic when not using Undo from 9e876ebc44fa6c5688b84e7d2ef661cfea5d2cc9. References #14982. --- .../java/org/mozilla/fenix/browser/BaseBrowserFragment.kt | 1 + app/src/main/java/org/mozilla/fenix/utils/Undo.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 352fcc522..9c6e9aa5d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -275,6 +275,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session { requireComponents.useCases.tabsUseCases.undo.invoke() }, + paddedForBottomToolbar = true, operation = { } ) } diff --git a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt index 6dd272f5a..7ba20701b 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt @@ -60,6 +60,7 @@ fun CoroutineScope.allowUndo( // writing a volatile variable. val requestedUndo = AtomicBoolean(false) + @Suppress("ComplexCondition") fun showUndoSnackbar() { val snackbar = FenixSnackbar .make( @@ -82,6 +83,7 @@ fun CoroutineScope.allowUndo( val shouldUseBottomToolbar = view.context.settings().shouldUseBottomToolbar val toolbarHeight = view.resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) + val dynamicToolbarEnabled = view.context.settings().isDynamicToolbarEnabled snackbar.view.updatePadding( bottom = if ( @@ -92,7 +94,7 @@ fun CoroutineScope.allowUndo( // can't intelligently position the snackbar on the upper most view. // Ideally we should not pass ContentFrameLayout in, but it's the only // way to display snackbars through a fragment transition. - view is ContentFrameLayout + (view is ContentFrameLayout || !dynamicToolbarEnabled) ) { toolbarHeight } else { From 4de466883b8818a10fa21023dff2684342067927 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Tue, 29 Sep 2020 10:59:17 +0200 Subject: [PATCH 29/30] Revert "For #12565: Pass bookmark storage to controller" for debug test failures. This reverts commit 3c22100b8495434ea8c30f9cef2bb8ce554f180b. --- .../library/bookmarks/BookmarkController.kt | 16 ++--- .../library/bookmarks/BookmarkFragment.kt | 2 - .../bookmarks/BookmarkControllerTest.kt | 72 ++++++++++--------- .../fenix/share/ShareControllerTest.kt | 10 ++- 4 files changed, 48 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt index cdf019aab..842d1878c 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt @@ -14,14 +14,14 @@ import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.concept.storage.BookmarkNode -import mozilla.components.concept.storage.BookmarksStorage -import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.sync.SyncReason import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.ext.bookmarkStorage +import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav /** @@ -52,8 +52,6 @@ interface BookmarkController { @Suppress("TooManyFunctions") class DefaultBookmarkController( private val activity: HomeActivity, - private val bookmarkStorage: BookmarksStorage, - private val accountManager: FxaAccountManager, private val navController: NavController, private val clipboardManager: ClipboardManager?, private val scope: CoroutineScope, @@ -145,14 +143,14 @@ class DefaultBookmarkController( scope.launch { store.dispatch(BookmarkFragmentAction.StartSync) invokePendingDeletion() - accountManager.syncNow(SyncReason.User) + activity.components.backgroundServices.accountManager.syncNow(SyncReason.User) // The current bookmark node we are viewing may be made invalid after syncing so we // check if the current node is valid and if it isn't we find the nearest valid ancestor // and open it val validAncestorGuid = store.state.guidBackstack.findLast { guid -> - bookmarkStorage.getBookmark(guid) != null + activity.bookmarkStorage.getBookmark(guid) != null } ?: BookmarkRoot.Mobile.id - val node = bookmarkStorage.getBookmark(validAncestorGuid)!! + val node = activity.bookmarkStorage.getBookmark(validAncestorGuid)!! handleBookmarkExpand(node) store.dispatch(BookmarkFragmentAction.FinishSync) } @@ -162,12 +160,12 @@ class DefaultBookmarkController( invokePendingDeletion.invoke() scope.launch { val parentGuid = store.state.guidBackstack.findLast { guid -> - store.state.tree?.guid != guid && bookmarkStorage.getBookmark(guid) != null + store.state.tree?.guid != guid && activity.bookmarkStorage.getBookmark(guid) != null } if (parentGuid == null) { navController.popBackStack() } else { - val parent = bookmarkStorage.getBookmark(parentGuid)!! + val parent = activity.bookmarkStorage.getBookmark(parentGuid)!! handleBookmarkExpand(parent) } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt index 054635f70..18c19943e 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt @@ -88,8 +88,6 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan _bookmarkInteractor = BookmarkFragmentInteractor( bookmarksController = DefaultBookmarkController( activity = requireActivity() as HomeActivity, - bookmarkStorage = requireComponents.core.bookmarksStorage, - accountManager = requireComponents.backgroundServices.accountManager, navController = findNavController(), clipboardManager = requireContext().getSystemService(), scope = viewLifecycleOwner.lifecycleScope, diff --git a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt index dcc41ec74..2d0f2deb4 100644 --- a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt @@ -6,17 +6,20 @@ package org.mozilla.fenix.library.bookmarks import android.content.ClipData import android.content.ClipboardManager +import android.content.Context import androidx.navigation.NavController +import androidx.navigation.NavDestination import androidx.navigation.NavDirections -import io.mockk.MockKAnnotations +import io.mockk.Runs import io.mockk.called import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every -import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.runs +import io.mockk.slot +import io.mockk.spyk import io.mockk.verify import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -24,8 +27,6 @@ import kotlinx.coroutines.test.TestCoroutineScope import mozilla.appservices.places.BookmarkRoot import mozilla.components.concept.storage.BookmarkNode import mozilla.components.concept.storage.BookmarkNodeType -import mozilla.components.concept.storage.BookmarksStorage -import mozilla.components.service.fxa.manager.FxaAccountManager import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -34,28 +35,32 @@ import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.Services import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.ext.bookmarkStorage +import org.mozilla.fenix.ext.components +@Suppress("TooManyFunctions", "LargeClass") @ExperimentalCoroutinesApi class BookmarkControllerTest { - private val scope = TestCoroutineScope() - - @MockK private lateinit var bookmarkStore: BookmarkFragmentStore - @MockK private lateinit var sharedViewModel: BookmarksSharedViewModel - @MockK(relaxUnitFun = true) private lateinit var clipboardManager: ClipboardManager - @MockK(relaxed = true) private lateinit var homeActivity: HomeActivity - @MockK(relaxed = true) private lateinit var bookmarkStorage: BookmarksStorage - @MockK(relaxed = true) private lateinit var accountManager: FxaAccountManager - @MockK(relaxed = true) private lateinit var navController: NavController - @MockK(relaxed = true) private lateinit var loadBookmarkNode: suspend (String) -> BookmarkNode? - @MockK(relaxed = true) private lateinit var showSnackbar: (String) -> Unit - @MockK(relaxed = true) private lateinit var deleteBookmarkNodes: (Set, Event) -> Unit - @MockK(relaxed = true) private lateinit var deleteBookmarkFolder: (Set) -> Unit - @MockK(relaxed = true) private lateinit var invokePendingDeletion: () -> Unit - private lateinit var controller: BookmarkController + private val bookmarkStore = spyk(BookmarkFragmentStore(BookmarkFragmentState(null))) + private val context: Context = mockk(relaxed = true) + private val scope = TestCoroutineScope() + private val clipboardManager: ClipboardManager = mockk(relaxUnitFun = true) + private val navController: NavController = mockk(relaxed = true) + private val sharedViewModel: BookmarksSharedViewModel = mockk() + private val loadBookmarkNode: suspend (String) -> BookmarkNode? = mockk(relaxed = true) + private val showSnackbar: (String) -> Unit = mockk(relaxed = true) + private val deleteBookmarkNodes: (Set, Event) -> Unit = mockk(relaxed = true) + private val deleteBookmarkFolder: (Set) -> Unit = mockk(relaxed = true) + private val invokePendingDeletion: () -> Unit = mockk(relaxed = true) + + private val homeActivity: HomeActivity = mockk(relaxed = true) + private val services: Services = mockk(relaxed = true) + private val item = BookmarkNode(BookmarkNodeType.ITEM, "456", "123", 0, "Mozilla", "http://mozilla.org", null) private val subfolder = @@ -84,20 +89,15 @@ class BookmarkControllerTest { @Before fun setup() { - MockKAnnotations.init(this) - loadBookmarkNode = mockk(relaxed = true) - - every { navController.currentDestination } returns mockk { - every { id } returns R.id.bookmarkFragment + every { homeActivity.components.services } returns services + every { navController.currentDestination } returns NavDestination("").apply { + id = R.id.bookmarkFragment } - every { bookmarkStore.state } returns BookmarkFragmentState(null) every { bookmarkStore.dispatch(any()) } returns mockk() every { sharedViewModel.selectedFolder = any() } just runs controller = DefaultBookmarkController( activity = homeActivity, - bookmarkStorage = bookmarkStorage, - accountManager = accountManager, navController = navController, clipboardManager = clipboardManager, scope = scope, @@ -211,12 +211,12 @@ class BookmarkControllerTest { @Test fun `handleBookmarkSelected should show a toast when selecting a root folder`() { - every { homeActivity.resources.getString(R.string.bookmark_cannot_edit_root) } returns "Can't edit default folders" + val errorMessage = context.getString(R.string.bookmark_cannot_edit_root) controller.handleBookmarkSelected(root) verify { - showSnackbar("Can't edit default folders") + showSnackbar(errorMessage) } } @@ -240,24 +240,25 @@ class BookmarkControllerTest { @Test fun `handleCopyUrl should copy bookmark url to clipboard and show a toast`() { - every { homeActivity.resources.getString(R.string.url_copied) } returns "URL copied" + val urlCopiedMessage = context.getString(R.string.url_copied) controller.handleCopyUrl(item) verifyOrder { ClipData.newPlainText(item.url, item.url) - showSnackbar("URL copied") + showSnackbar(urlCopiedMessage) } } @Test fun `handleBookmarkSharing should navigate to the 'Share' fragment`() { + val navDirectionsSlot = slot() + every { navController.navigate(capture(navDirectionsSlot), null) } just Runs + controller.handleBookmarkSharing(item) verify { - navController.navigate(withArg { - assertEquals(R.id.action_global_shareFragment, it.actionId) - }, null) + navController.navigate(navDirectionsSlot.captured, null) } } @@ -312,7 +313,8 @@ class BookmarkControllerTest { @Test fun `handleRequestSync dispatches actions in the correct order`() { - coEvery { bookmarkStorage.getBookmark(any()) } returns tree + every { homeActivity.components.backgroundServices.accountManager } returns mockk(relaxed = true) + coEvery { homeActivity.bookmarkStorage.getBookmark(any()) } returns tree coEvery { loadBookmarkNode.invoke(any()) } returns tree controller.handleRequestSync() diff --git a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt index a66336b71..9950dd5b6 100644 --- a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt @@ -83,13 +83,11 @@ class ShareControllerTest { @Test fun `handleShareToApp should start a new sharing activity and close this`() = runBlocking { - val appPackageName = "package" - val appClassName = "activity" val appShareOption = AppShareOption( name = "app", icon = mockk(), - packageName = appPackageName, - activityName = appClassName + packageName = "package", + activityName = "activity" ) val shareIntent = slot() // Our share Intent uses `FLAG_ACTIVITY_NEW_TASK` but when resolving the startActivity call @@ -110,8 +108,8 @@ class ShareControllerTest { assertEquals(textToShare, shareIntent.captured.extras!![Intent.EXTRA_TEXT]) assertEquals("text/plain", shareIntent.captured.type) assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, shareIntent.captured.flags) - assertEquals(appPackageName, shareIntent.captured.component!!.packageName) - assertEquals(appClassName, shareIntent.captured.component!!.className) + assertEquals("package", shareIntent.captured.component!!.packageName) + assertEquals("activity", shareIntent.captured.component!!.className) verify { recentAppStorage.updateRecentApp(appShareOption.activityName) } verifyOrder { From 2fda22e85785fe30c2e1084349159ddc3b065ec7 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Tue, 29 Sep 2020 10:59:31 +0200 Subject: [PATCH 30/30] Revert "For #12565: Pass metrics to share controller" for debug test failures. This reverts commit bbaca062741fdfb9044fca3ef1f2853056f0d057. --- .../mozilla/fenix/share/ShareController.kt | 7 +- .../org/mozilla/fenix/share/ShareFragment.kt | 1 - .../fenix/share/ShareControllerTest.kt | 133 ++++++++---------- 3 files changed, 64 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt index 35635caf1..e3c1a7310 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt @@ -29,7 +29,7 @@ import mozilla.components.support.ktx.kotlin.isExtensionUrl import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.share.listadapters.AppShareOption @@ -66,7 +66,6 @@ interface ShareController { @Suppress("TooManyFunctions") class DefaultShareController( private val context: Context, - private val metrics: MetricController, private val shareSubject: String?, private val shareData: List, private val sendTabUseCases: SendTabUseCases, @@ -123,7 +122,7 @@ class DefaultShareController( } override fun handleShareToDevice(device: Device) { - metrics.track(Event.SendTab) + context.metrics.track(Event.SendTab) shareToDevicesWithRetry { sendTabUseCases.sendToDeviceAsync(device.id, shareData.toTabData()) } } @@ -132,7 +131,7 @@ class DefaultShareController( } override fun handleSignIn() { - metrics.track(Event.SignInToSendTab) + context.metrics.track(Event.SignInToSendTab) val directions = ShareFragmentDirections.actionGlobalTurnOnSync(padSnackbar = true) navController.nav(R.id.shareFragment, directions) diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt b/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt index 0a4ed5ae0..cd190d564 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt @@ -67,7 +67,6 @@ class ShareFragment : AppCompatDialogFragment() { shareInteractor = ShareInteractor( DefaultShareController( context = requireContext(), - metrics = requireComponents.analytics.metrics, shareSubject = args.shareSubject, shareData = shareData, snackbar = FenixSnackbar.make( diff --git a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt index 9950dd5b6..90540b24c 100644 --- a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt @@ -10,13 +10,12 @@ import android.content.Context import android.content.Intent import androidx.navigation.NavController import com.google.android.material.snackbar.Snackbar -import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.every -import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.slot +import io.mockk.spyk import io.mockk.verify import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -39,6 +38,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.share.listadapters.AppShareOption @@ -46,15 +46,9 @@ import org.mozilla.fenix.share.listadapters.AppShareOption @RunWith(FenixRobolectricTestRunner::class) @ExperimentalCoroutinesApi class ShareControllerTest { - - @MockK(relaxed = true) private lateinit var metrics: MetricController - @MockK(relaxed = true) private lateinit var sendTabUseCases: SendTabUseCases - @MockK(relaxed = true) private lateinit var snackbar: FenixSnackbar - @MockK(relaxed = true) private lateinit var navController: NavController - @MockK(relaxed = true) private lateinit var dismiss: (ShareController.Result) -> Unit - @MockK private lateinit var recentAppStorage: RecentAppsStorage - - private val context: Context = testContext + // Need a valid context to retrieve Strings for example, but we also need it to return our "metrics" + private val context: Context = spyk(testContext) + private val metrics: MetricController = mockk(relaxed = true) private val shareSubject = "shareSubject" private val shareData = listOf( ShareData(url = "url0", title = "title0"), @@ -67,15 +61,23 @@ class ShareControllerTest { ) private val textToShare = "${shareData[0].url}\n\n${shareData[1].url}" private val testCoroutineScope = TestCoroutineScope() + private val sendTabUseCases = mockk(relaxed = true) + private val snackbar = mockk(relaxed = true) + private val navController = mockk(relaxed = true) + private val dismiss = mockk<(ShareController.Result) -> Unit>(relaxed = true) + private val recentAppStorage = mockk(relaxed = true) + private val controller = DefaultShareController( + context, shareSubject, shareData, sendTabUseCases, snackbar, navController, + recentAppStorage, testCoroutineScope, dismiss + ) @Before fun setUp() { - MockKAnnotations.init(this) + every { context.metrics } returns metrics } @Test fun `handleShareClosed should call a passed in delegate to close this`() { - val controller = buildController() controller.handleShareClosed() verify { dismiss(ShareController.Result.DISMISSED) } @@ -83,20 +85,17 @@ class ShareControllerTest { @Test fun `handleShareToApp should start a new sharing activity and close this`() = runBlocking { - val appShareOption = AppShareOption( - name = "app", - icon = mockk(), - packageName = "package", - activityName = "activity" - ) + val appPackageName = "package" + val appClassName = "activity" + val appShareOption = AppShareOption("app", mockk(), appPackageName, appClassName) val shareIntent = slot() // Our share Intent uses `FLAG_ACTIVITY_NEW_TASK` but when resolving the startActivity call // needed for capturing the actual Intent used the `slot` one doesn't have this flag so we // need to use an Activity Context. - val activityContext: Context = mockk { - every { startActivity(capture(shareIntent)) } just Runs - } - val testController = buildController(context = activityContext) + val activityContext: Context = mockk() + val testController = DefaultShareController(activityContext, shareSubject, shareData, mockk(), + mockk(), mockk(), recentAppStorage, testCoroutineScope, dismiss) + every { activityContext.startActivity(capture(shareIntent)) } just Runs every { recentAppStorage.updateRecentApp(appShareOption.activityName) } just Runs testController.handleShareToApp(appShareOption) @@ -108,8 +107,8 @@ class ShareControllerTest { assertEquals(textToShare, shareIntent.captured.extras!![Intent.EXTRA_TEXT]) assertEquals("text/plain", shareIntent.captured.type) assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, shareIntent.captured.flags) - assertEquals("package", shareIntent.captured.component!!.packageName) - assertEquals("activity", shareIntent.captured.component!!.className) + assertEquals(appPackageName, shareIntent.captured.component!!.packageName) + assertEquals(appClassName, shareIntent.captured.component!!.className) verify { recentAppStorage.updateRecentApp(appShareOption.activityName) } verifyOrder { @@ -120,21 +119,18 @@ class ShareControllerTest { @Test fun `handleShareToApp should dismiss with an error start when a security exception occurs`() { - val appShareOption = AppShareOption( - name = "app", - icon = mockk(), - packageName = "package", - activityName = "activity" - ) + val appPackageName = "package" + val appClassName = "activity" + val appShareOption = AppShareOption("app", mockk(), appPackageName, appClassName) val shareIntent = slot() // Our share Intent uses `FLAG_ACTIVITY_NEW_TASK` but when resolving the startActivity call // needed for capturing the actual Intent used the `slot` one doesn't have this flag so we // need to use an Activity Context. - val activityContext: Context = mockk { - every { startActivity(capture(shareIntent)) } throws SecurityException() - every { getString(R.string.share_error_snackbar) } returns "Cannot share to this app" - } - val testController = buildController(context = activityContext) + val activityContext: Context = mockk() + val testController = DefaultShareController(activityContext, shareSubject, shareData, mockk(), + snackbar, mockk(), mockk(), testCoroutineScope, dismiss) + every { activityContext.startActivity(capture(shareIntent)) } throws SecurityException() + every { activityContext.getString(R.string.share_error_snackbar) } returns "Cannot share to this app" testController.handleShareToApp(appShareOption) @@ -179,7 +175,6 @@ class ShareControllerTest { val deviceId = slot() val tabsShared = slot>() - val controller = buildController() controller.handleShareToDevice(deviceToShareTo) // Verify all the needed methods are called. @@ -204,7 +199,6 @@ class ShareControllerTest { ) val tabsShared = slot>() - val controller = buildController() controller.handleShareToAllDevices(devicesToShareTo) verifyOrder { @@ -219,7 +213,6 @@ class ShareControllerTest { @Test fun `handleSignIn should navigate to the Sync Fragment and dismiss this one`() { - val controller = buildController() controller.handleSignIn() verifyOrder { @@ -234,7 +227,6 @@ class ShareControllerTest { @Test fun `handleReauth should navigate to the Account Problem Fragment and dismiss this one`() { - val controller = buildController() controller.handleReauth() verifyOrder { @@ -248,7 +240,6 @@ class ShareControllerTest { @Test fun `showSuccess should show a snackbar with a success message`() { - val controller = buildController() val expectedMessage = controller.getSuccessMessage() val expectedTimeout = Snackbar.LENGTH_SHORT @@ -268,7 +259,7 @@ class ShareControllerTest { val expectedRetryMessage = context.getString(R.string.sync_sent_tab_error_snackbar_action) - buildController().showFailureWithRetryOption(operation) + controller.showFailureWithRetryOption(operation) verify { snackbar.apply { @@ -282,12 +273,18 @@ class ShareControllerTest { @Test fun `getSuccessMessage should return different strings depending on the number of shared tabs`() { - val controllerWithOneSharedTab = buildController( - data = listOf(ShareData(url = "url0", title = "title0")) - ) - val controllerWithMoreSharedTabs = buildController( - data = shareData + val controllerWithOneSharedTab = DefaultShareController( + context, + shareSubject, + listOf(ShareData(url = "url0", title = "title0")), + mockk(), + mockk(), + mockk(), + mockk(), + mockk(), + mockk() ) + val controllerWithMoreSharedTabs = controller val expectedTabSharedMessage = context.getString(R.string.sync_sent_tab_snackbar) val expectedTabsSharedMessage = context.getString(R.string.sync_sent_tabs_snackbar) @@ -301,7 +298,7 @@ class ShareControllerTest { @Test fun `getShareText should respect concatenate shared tabs urls`() { - assertEquals(textToShare, buildController(data = shareData).getShareText()) + assertEquals(textToShare, controller.getShareText()) } @Test @@ -311,7 +308,10 @@ class ShareControllerTest { ShareData(url = "moz-extension://eb8df45a-895b-4f3a-896a-c0c71ae5/page.html?url=url0"), ShareData(url = "url1") ) - val controller = buildController(data = shareData) + val controller = DefaultShareController( + context, shareSubject, shareData, sendTabUseCases, snackbar, navController, + recentAppStorage, testCoroutineScope, dismiss + ) val expectedShareText = "${shareData[0].url}\n\nurl0\n\n${shareData[2].url}" assertEquals(expectedShareText, controller.getShareText()) @@ -319,20 +319,25 @@ class ShareControllerTest { @Test fun `getShareSubject will return "shareSubject" if that is non null`() { - assertEquals(shareSubject, buildController(subject = shareSubject).getShareSubject()) + assertEquals(shareSubject, controller.getShareSubject()) } @Test fun `getShareSubject will return a concatenation of tab titles if "shareSubject" is null`() { - val controller = buildController(subject = null) + val controller = DefaultShareController( + context, null, shareData, sendTabUseCases, snackbar, navController, + recentAppStorage, testCoroutineScope, dismiss + ) assertEquals("title0, title1", controller.getShareSubject()) } @Test fun `ShareTab#toTabData maps a list of ShareTab to a TabData list`() { - val tabData = with(buildController()) { - shareData.toTabData() + var tabData: List + + with(controller) { + tabData = shareData.toTabData() } assertEquals(tabsData, tabData) @@ -340,13 +345,14 @@ class ShareControllerTest { @Test fun `ShareTab#toTabData creates a data url from text if no url is specified`() { + var tabData: List val expected = listOf( TabData(title = "title0", url = ""), TabData(title = "title1", url = "data:,Hello%2C%20World!") ) - val tabData = with(buildController()) { - listOf( + with(controller) { + tabData = listOf( ShareData(title = "title0"), ShareData(title = "title1", text = "Hello, World!") ).toTabData() @@ -354,21 +360,4 @@ class ShareControllerTest { assertEquals(expected, tabData) } - - private fun buildController( - context: Context = this.context, - subject: String? = shareSubject, - data: List = shareData - ) = DefaultShareController( - context = context, - metrics = metrics, - shareSubject = subject, - shareData = data, - sendTabUseCases = sendTabUseCases, - snackbar = snackbar, - navController = navController, - recentAppsStorage = recentAppStorage, - viewLifecycleScope = testCoroutineScope, - dismiss = dismiss - ) }