diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2021-03-17 10:30:50 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-03-17 10:30:50 +0000 |
commit | 681ac47459b67f6bc6dc264541a33a282fa5fe33 (patch) | |
tree | b3b854607cd0a278b833f66f7c123dd4602011e5 | |
parent | 3d13d1f99990e0d5e8ce0e27860e96c5dea15154 (diff) | |
parent | 57ac59568cb0b6aea0e5ed1fc5623a405813a876 (diff) | |
download | base-681ac47459b67f6bc6dc264541a33a282fa5fe33.tar.gz |
Snap for 7214754 from 57ac59568cb0b6aea0e5ed1fc5623a405813a876 to mainline-permission-releaseandroid-mainline-11.0.0_r25
Change-Id: I4988a64029abe616aa64cf767be5901e646d2764
35 files changed, 846 insertions, 534 deletions
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 833d77885b8c..83a60788f26b 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1705,7 +1705,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Каб пераключыцца на іншую функцыю, правядзіце ўверх трыма пальцамі і ўтрымлівайце іх на экране."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Павелічэнне"</string> <string name="user_switched" msgid="7249833311585228097">"Бягучы карыстальнік <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"Пераход да <xliff:g id="NAME">%1$s</xliff:g>..."</string> + <string name="user_switching_message" msgid="1912993630661332336">"Пераход у рэжым \"<xliff:g id="NAME">%1$s</xliff:g>\"..."</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> выходзіць з сістэмы…"</string> <string name="owner_name" msgid="8713560351570795743">"Уладальнік"</string> <string name="error_message_title" msgid="4082495589294631966">"Памылка"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 99d7d8651d1e..a38f072eec07 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1661,9 +1661,9 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"За превключване между функциите прекарайте три пръста нагоре и задръжте."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Ниво на мащаба"</string> <string name="user_switched" msgid="7249833311585228097">"Текущ потребител <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"Превключва се към <xliff:g id="NAME">%1$s</xliff:g>…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"Превключва се към: <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> излиза…"</string> - <string name="owner_name" msgid="8713560351570795743">"собственик"</string> + <string name="owner_name" msgid="8713560351570795743">"Собственик"</string> <string name="error_message_title" msgid="4082495589294631966">"Грешка"</string> <string name="error_message_change_not_allowed" msgid="843159705042381454">"Тази промяна не е разрешена от администратора ви"</string> <string name="app_not_found" msgid="3429506115332341800">"Няма намерено приложение за извършване на това действие"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index b95cab1aa3ae..e781287cefdf 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"একটি ফিচার থেকে অন্যটিতে যেতে, তিনটি আঙ্গুল দিয়ে উপরের দিকে সোয়াইপ করে ধরে থাকুন।"</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"বড় করে দেখা"</string> <string name="user_switched" msgid="7249833311585228097">"বর্তমান ব্যবহারকারী <xliff:g id="NAME">%1$s</xliff:g>৷"</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> নামের ব্যবহারকারীতে যাচ্ছে…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"ব্যবহারকারী পরিবর্তন করে <xliff:g id="NAME">%1$s</xliff:g> করা হচ্ছে…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>কে লগ-আউট করা হচ্ছে..."</string> <string name="owner_name" msgid="8713560351570795743">"মালিক"</string> <string name="error_message_title" msgid="4082495589294631966">"ত্রুটি"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 168ea08acc6d..a1607c4628f9 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1705,7 +1705,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Chcete-li přepnout mezi funkcemi, přejeďte nahoru třemi prsty a podržte je."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Zvětšení"</string> <string name="user_switched" msgid="7249833311585228097">"Aktuální uživatel je <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"Přepínání na účet <xliff:g id="NAME">%1$s</xliff:g>…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"Přepínání na uživatele <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Odhlašování uživatele <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"Vlastník"</string> <string name="error_message_title" msgid="4082495589294631966">"Chyba"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index a0be496f5f3c..7fe3c24a94fd 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1539,8 +1539,8 @@ <string name="expires_on" msgid="1623640879705103121">"Expira el:"</string> <string name="serial_number" msgid="3479576915806623429">"Número de serie:"</string> <string name="fingerprints" msgid="148690767172613723">"Huellas digitales:"</string> - <string name="sha256_fingerprint" msgid="7103976380961964600">"Huella dactilar SHA-256"</string> - <string name="sha1_fingerprint" msgid="2339915142825390774">"Huella dactilar SHA-1:"</string> + <string name="sha256_fingerprint" msgid="7103976380961964600">"Huella digital SHA-256"</string> + <string name="sha1_fingerprint" msgid="2339915142825390774">"Huella digital SHA-1:"</string> <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"Ver todas"</string> <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Elige actividad"</string> <string name="share_action_provider_share_with" msgid="1904096863622941880">"Compartir con"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index bcf91e1ce564..ee56fb201fff 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -192,7 +192,7 @@ <string name="network_logging_notification_title" msgid="554983187553845004">"Jabeak kudeatzen du gailua"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Erakundeak kudeatzen du gailua eta baliteke sareko trafikoa gainbegiratzea. Sakatu hau xehetasunak ikusteko."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikazioek zure kokapena atzi dezakete"</string> - <string name="location_changed_notification_text" msgid="7158423339982706912">"Informazio gehiago lortzeko, jo IKT sailaren administratzailearengana"</string> + <string name="location_changed_notification_text" msgid="7158423339982706912">"Informazio gehiago lortzeko, jo IKT saileko administratzailearengana"</string> <string name="country_detector" msgid="7023275114706088854">"Herrialde-hautemailea"</string> <string name="location_service" msgid="2439187616018455546">"Kokapen-zerbitzua"</string> <string name="sensor_notification_service" msgid="7474531979178682676">"Sentsorearen jakinarazpen-zerbitzua"</string> @@ -305,8 +305,8 @@ <string name="permgroupdesc_storage" msgid="6351503740613026600">"atzitu gailuko argazkiak, multimedia-edukia eta fitxategiak"</string> <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofonoa"</string> <string name="permgroupdesc_microphone" msgid="1047786732792487722">"grabatu audioa"</string> - <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Ariketa fisikoa"</string> - <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"ariketa fisikoak atzitu"</string> + <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Jarduera fisiko"</string> + <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"jarduera fisikoa atzitu"</string> <string name="permgrouplab_camera" msgid="9090413408963547706">"Kamera"</string> <string name="permgroupdesc_camera" msgid="7585150538459320326">"atera argazkiak eta grabatu bideoak"</string> <string name="permgrouplab_calllog" msgid="7926834372073550288">"Deien erregistroa"</string> @@ -433,8 +433,8 @@ <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aplikazioak edonoiz erabil dezake mikrofonoa audioa grabatzeko."</string> <string name="permlab_sim_communication" msgid="176788115994050692">"bidali aginduak SIM txartelera"</string> <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM txartelera aginduak bidaltzeko aukera ematen die aplikazioei. Oso arriskutsua da."</string> - <string name="permlab_activityRecognition" msgid="1782303296053990884">"hauteman ariketa fisikoa"</string> - <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aplikazioak ariketa fisikoa hauteman dezake."</string> + <string name="permlab_activityRecognition" msgid="1782303296053990884">"hauteman jarduera fisiko"</string> + <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aplikazioak jarduera fisiko hauteman dezake."</string> <string name="permlab_camera" msgid="6320282492904119413">"atera argazkiak eta grabatu bideoak"</string> <string name="permdesc_camera" msgid="1354600178048761499">"Aplikazioak edonoiz erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko."</string> <string name="permlab_systemCamera" msgid="3642917457796210580">"eman sistemako kamerak atzitzeko baimena aplikazio edo zerbitzu bati argazkiak ateratzeko eta bideoak grabatzeko"</string> @@ -1183,7 +1183,7 @@ <string name="unsupported_display_size_show" msgid="980129850974919375">"Erakutsi beti"</string> <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"Android sistema eragilearen bertsio bateraezin baterako dago egina <xliff:g id="APP_NAME">%1$s</xliff:g>; beraz, espero ez bezala funtziona lezake. Baliteke aplikazioaren bertsio eguneratuago bat eskuragarri egotea."</string> <string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"Erakutsi beti"</string> - <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Bilatu eguneratzea"</string> + <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Bilatu eguneratzeak"</string> <string name="smv_application" msgid="3775183542777792638">"<xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioak (<xliff:g id="PROCESS">%2$s</xliff:g> prozesua) berak aplikatutako StrictMode gidalerroa urratu du."</string> <string name="smv_process" msgid="1398801497130695446">"<xliff:g id="PROCESS">%1$s</xliff:g> prozesuak bere kabuz ezarritako StrictMode gidalerroak urratu ditu."</string> <string name="android_upgrading_title" product="default" msgid="7279077384220829683">"Telefonoa eguneratzen ari da…"</string> @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Eginbide batetik bestera aldatzeko, pasatu hiru hatz pantailaren behealdetik gora eta eduki sakatuta une batez."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Lupa"</string> <string name="user_switched" msgid="7249833311585228097">"Erabiltzailea: <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> erabiltzailera aldatzen…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"\"<xliff:g id="NAME">%1$s</xliff:g>\" erabiltzailera aldatzen…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> erabiltzailearen saioa amaitzen…"</string> <string name="owner_name" msgid="8713560351570795743">"Jabea"</string> <string name="error_message_title" msgid="4082495589294631966">"Errorea"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 3f6fc2467abe..9058ada914d3 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1625,7 +1625,7 @@ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ویژگیهای دسترسپذیری روشن شود؟"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"با پایین نگه داشتن هردو کلید میزان صدا بهمدت چند ثانیه، ویژگیهای دسترسپذیری روشن میشود. با این کار نحوه عملکرد دستگاهتان تغییر میکند.\n\nویژگیهای فعلی:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nمیتوانید ویژگیهای انتخابی را در «تنظیمات > دسترسپذیری» تغییر دهید."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> روشن شود؟"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"سرویس <xliff:g id="SERVICE">%1$s</xliff:g> روشن شود؟"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"با پایین نگه داشتن هردو کلید میزان صدا بهمدت چند ثانیه، <xliff:g id="SERVICE">%1$s</xliff:g> (یکی از ویژگیهای دسترسپذیری) روشن میشود. با این کار نحوه عملکرد دستگاهتان تغییر میکند.\n\nمیتوانید در «تنظیمات > دسترسپذیری»،این میانبر را به ویژگی دیگری تغییر دهید."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"روشن شود"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"روشن نشود"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index ba66d99ab7d7..8d399aa351bc 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Pour basculer entre les fonctionnalités, balayez l\'écran vers le haut avec trois doigts et maintenez-les-y."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Zoom"</string> <string name="user_switched" msgid="7249833311585228097">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string> - <string name="user_switching_message" msgid="1912993630661332336">"Changement d\'utilisateur (<xliff:g id="NAME">%1$s</xliff:g>) en cours…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"Passage au profil : <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Déconnexion de <xliff:g id="NAME">%1$s</xliff:g> en cours..."</string> <string name="owner_name" msgid="8713560351570795743">"Propriétaire"</string> <string name="error_message_title" msgid="4082495589294631966">"Erreur"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 4776475aee6e..d85da9430f9f 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Pour changer de fonctionnalité, balayez l\'écran vers le haut avec trois doigts et appuyez de manière prolongée."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Agrandissement"</string> <string name="user_switched" msgid="7249833311585228097">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string> - <string name="user_switching_message" msgid="1912993630661332336">"Chargement du profil de <xliff:g id="NAME">%1$s</xliff:g>..."</string> + <string name="user_switching_message" msgid="1912993630661332336">"Passage au profil : <xliff:g id="NAME">%1$s</xliff:g>..."</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Déconnexion de <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"Propriétaire"</string> <string name="error_message_title" msgid="4082495589294631966">"Erreur"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 9ef82fc6d325..8ab4eb677f69 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -637,7 +637,7 @@ <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"એપ્લિકેશનને કૉલમાં વપરાશકર્તા અનુભવ પ્રદાન કરવાની મંજૂરી આપે છે."</string> <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"ઐતિહાસિક નેટવર્ક ઉપયોગ વાંચો"</string> <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"એપ્લિકેશનને ચોક્કસ નેટવર્ક્સ અને ઍપ્લિકેશનો માટે ઐતિહાસિક નેટવર્ક વપરાશ વાંચવાની મંજૂરી આપે છે."</string> - <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"નેટવર્ક નીતિ મેનેજ કરો"</string> + <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"નેટવર્ક પૉલિસી મેનેજ કરો"</string> <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"ઍપને નેટવર્ક નીતિઓ મેનેજ કરવાની અને ઍપ-વિશિષ્ટ નિયમો નિર્ધારિત કરવાની મંજૂરી આપે છે."</string> <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"નેટવર્ક વપરાશ એકાઉન્ટિંગ સંશોધિત કરો"</string> <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"એપ્લિકેશનને કેવી રીતે ઍપ્લિકેશનો સામે નેટવર્ક વપરાશ ગણવામાં આવે છે તે સંશોધિત કરવાની મંજૂરી આપે છે. સામાન્ય ઍપ્લિકેશનો દ્વારા ઉપયોગમાં લેવા માટે નથી."</string> @@ -691,7 +691,7 @@ <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"ચેતવણી વિના આ Android TV ડિવાઇસ પર રહેલો આ વપરાશકર્તાનો ડેટા કાઢી નાખો."</string> <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"ચેતવણી વિના આ ફોન પરનો આ વપરાશકર્તાનો ડેટા કાઢી નાખો."</string> <string name="policylab_setGlobalProxy" msgid="215332221188670221">"ઉપકરણ વૈશ્વિક પ્રોક્સી સેટ કરો"</string> - <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"નીતિ સક્ષમ હોય તે વખતે ઉપયોગ કરવા માટેના ઉપકરણ વૈશ્વિક પ્રોક્સીને સેટ કરો. ફક્ત ઉપકરણના માલિક વૈશ્વિક પ્રોક્સી સેટ કરી શકે છે."</string> + <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"પૉલિસી સક્ષમ હોય તે વખતે ઉપયોગ કરવા માટેના ડિવાઇસ વૈશ્વિક પ્રોક્સીને સેટ કરો. ફક્ત ડિવાઇસના માલિક વૈશ્વિક પ્રોક્સી સેટ કરી શકે છે."</string> <string name="policylab_expirePassword" msgid="6015404400532459169">"સ્ક્રીન લૉક પાસવર્ડ સમાપ્તિ સેટ કરો"</string> <string name="policydesc_expirePassword" msgid="9136524319325960675">"કેટલા સમયાંતરે સ્ક્રીન લૉક પાસવર્ડ, પિન અથવા પૅટર્ન બદલવો આવશ્યક છે, તેને બદલો."</string> <string name="policylab_encryptedStorage" msgid="9012936958126670110">"સંગ્રહ એન્ક્રિપ્શન સેટ કરો"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index fb1e91e68679..7e52455cde84 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1618,7 +1618,7 @@ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"आपने अपना लॉक खोलने का पैटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> बार गलत तरीके से बनाया है. <xliff:g id="NUMBER_1">%2$d</xliff:g> और गलत प्रयासों के बाद, आपसे अपने Android TV डिवाइस को अपने ईमेल खाते का इस्तेमाल करके अनलॉक करने के लिए कहा जाएगा.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकंड बाद प्रयास करें."</string> <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"आपने अपने अनलॉक आकार को <xliff:g id="NUMBER_0">%1$d</xliff:g> बार गलत तरीके से आरेखित किया है. <xliff:g id="NUMBER_1">%2$d</xliff:g> और असफल प्रयासों के बाद, आपसे अपने फ़ोन को किसी ईमेल खाते का उपयोग करके अनलॉक करने के लिए कहा जाएगा.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकंड में फिर से प्रयास करें."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> - <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"निकालें"</string> + <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"हटाएं"</string> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"वॉल्यूम को सुझाए गए स्तर से ऊपर बढ़ाएं?\n\nअत्यधिक वॉल्यूम पर ज़्यादा समय तक सुनने से आपकी सुनने की क्षमता को नुकसान हो सकता है."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"सुलभता शॉर्टकट का इस्तेमाल करना चाहते हैं?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"शॉर्टकट के चालू होने पर, दाेनाें वॉल्यूम बटन (आवाज़ कम या ज़्यादा करने वाले बटन) को तीन सेकंड तक दबाने से, सुलभता सुविधा शुरू हाे जाएगी."</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index a5a323239ffe..58dc7a380362 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Մի գործառույթից մյուսին անցնելու համար երեք մատը սահեցրեք վերև և պահեք։"</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Խոշորացում"</string> <string name="user_switched" msgid="7249833311585228097">"Ներկայիս օգտատերը <xliff:g id="NAME">%1$s</xliff:g>:"</string> - <string name="user_switching_message" msgid="1912993630661332336">"Փոխարկվում է <xliff:g id="NAME">%1$s</xliff:g>-ին..."</string> + <string name="user_switching_message" msgid="1912993630661332336">"Անցում հետևյալ պրոֆիլին՝ <xliff:g id="NAME">%1$s</xliff:g>..."</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Ելք <xliff:g id="NAME">%1$s</xliff:g>-ից…"</string> <string name="owner_name" msgid="8713560351570795743">"Սեփականատեր"</string> <string name="error_message_title" msgid="4082495589294631966">"Սխալ"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index adf20ccdc941..83a6945eb4f6 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1329,7 +1329,7 @@ <string name="time_picker_dialog_title" msgid="9053376764985220821">"הגדרת שעה"</string> <string name="date_picker_dialog_title" msgid="5030520449243071926">"הגדרת תאריך"</string> <string name="date_time_set" msgid="4603445265164486816">"הגדרה"</string> - <string name="date_time_done" msgid="8363155889402873463">"בוצע"</string> + <string name="date_time_done" msgid="8363155889402873463">"סיום"</string> <string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"חדש: "</font></string> <string name="perms_description_app" msgid="2747752389870161996">"מטעם <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="no_permissions" msgid="5729199278862516390">"לא דרושים אישורים"</string> @@ -1662,7 +1662,7 @@ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"שרטטת קו ביטול נעילה שגוי <xliff:g id="NUMBER_0">%1$d</xliff:g> פעמים. לאחר <xliff:g id="NUMBER_1">%2$d</xliff:g> ניסיונות כושלים נוספים, תתבקש לבטל את הנעילה של מכשיר ה-Android TV באמצעות חשבון אימייל.\n\n נסה שוב בעוד <xliff:g id="NUMBER_2">%3$d</xliff:g> שניות."</string> <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"שרטטת את קו ביטול הנעילה באופן שגוי <xliff:g id="NUMBER_0">%1$d</xliff:g> פעמים. לאחר <xliff:g id="NUMBER_1">%2$d</xliff:g> ניסיונות כושלים נוספים, תתבקש לבטל את נעילת הטלפון באמצעות חשבון אימייל.\n\nנסה שוב בעוד <xliff:g id="NUMBER_2">%3$d</xliff:g> שניות."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> - <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"הסר"</string> + <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"הסרה"</string> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"האם להעלות את עוצמת הקול מעל לרמה המומלצת?\n\nהאזנה בעוצמת קול גבוהה למשכי זמן ממושכים עלולה לפגוע בשמיעה."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"להשתמש בקיצור הדרך לתכונת הנגישות?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"כשקיצור הדרך מופעל, לחיצה על שני לחצני עוצמת הקול למשך שלוש שניות מפעילה את תכונת הנגישות."</string> @@ -1705,7 +1705,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"כדי לעבור בין תכונות, יש להחליק כלפי מעלה בעזרת שלוש אצבעות ולהחזיק."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"הגדלה"</string> <string name="user_switched" msgid="7249833311585228097">"המשתמש הנוכחי <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"עובר אל <xliff:g id="NAME">%1$s</xliff:g>…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"מעבר אל <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"מתבצע ניתוק של <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"בעלים"</string> <string name="error_message_title" msgid="4082495589294631966">"שגיאה"</string> @@ -1820,7 +1820,7 @@ <string name="immersive_cling_title" msgid="2307034298721541791">"צפייה במסך מלא"</string> <string name="immersive_cling_description" msgid="7092737175345204832">"כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה."</string> <string name="immersive_cling_positive" msgid="7047498036346489883">"הבנתי"</string> - <string name="done_label" msgid="7283767013231718521">"בוצע"</string> + <string name="done_label" msgid="7283767013231718521">"סיום"</string> <string name="hour_picker_description" msgid="5153757582093524635">"מחוון שעות מעגלי"</string> <string name="minute_picker_description" msgid="9029797023621927294">"מחוון דקות מעגלי"</string> <string name="select_hours" msgid="5982889657313147347">"בחר שעות"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index a00eed1023d5..d18ad9079f43 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -665,7 +665,7 @@ <string name="permdesc_bindCarrierMessagingService" msgid="6316457028173478345">"Иесіне оператордың хабар алмасу қызметінің жоғарғы деңгейлі интерфейсіне байластыруға рұқсат етеді. Қалыпты қолданбалар үшін ешқашан қажет болмайды."</string> <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"оператор қызметтеріне қосылу"</string> <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Иесіне оператор қызметтеріне қосылуға мүмкіндік береді. Қалыпты қолданбалар үшін қажет болмайды."</string> - <string name="permlab_access_notification_policy" msgid="5524112842876975537">"\"Мазаламау\" режиміне кіру"</string> + <string name="permlab_access_notification_policy" msgid="5524112842876975537">"Мазаламау режиміне кіру"</string> <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Қолданбаға «Мазаламау» конфигурациясын оқу және жазу мүмкіндігін береді."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"рұқсаттарды пайдалану туралы деректерді көру"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Пайдаланушы қолданбаға берілетін рұқсаттарды басқара алады. Ондай рұқсаттар әдеттегі қолданбаларға керек емес."</string> @@ -1618,7 +1618,7 @@ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"Құлыпты ашу өрнегін <xliff:g id="NUMBER_0">%1$d</xliff:g> рет дұрыс сызбадыңыз. Енді тағы <xliff:g id="NUMBER_1">%2$d</xliff:g> рет қателессеңіз, Android TV құрылғыңыздың құлпын ашу үшін есептік жазбаңызға кіру керек болады.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секундтан кейін қайталап көріңіз."</string> <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Бекітпені ашу кескінін <xliff:g id="NUMBER_0">%1$d</xliff:g> рет қате сыздыңыз. <xliff:g id="NUMBER_1">%2$d</xliff:g> сәтсіз әрекеттен кейін телефоныңызды есептік жазба арқылы ашу өтінішін аласыз. \n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секундтан кейін қайта әрекеттеніңіз."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> - <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Алып тастау"</string> + <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Жою"</string> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дыбыс деңгейін ұсынылған деңгейден көтеру керек пе?\n\nЖоғары дыбыс деңгейінде ұзақ кезеңдер бойы тыңдау есту қабілетіңізге зиян тигізуі мүмкін."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Арнайы мүмкіндік төте жолын пайдалану керек пе?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Түймелер тіркесімі қосулы кезде, екі дыбыс түймесін 3 секунд басып тұрсаңыз, \"Арнайы мүмкіндіктер\" функциясы іске қосылады."</string> @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Бір функциядан екінші функцияға ауысу үшін үш саусақпен жоғары қарай сырғытып, ұстап тұрыңыз."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Ұлғайту"</string> <string name="user_switched" msgid="7249833311585228097">"Ағымдағы пайдаланушы <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> ауысу орындалуда…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> профиліне ауысу…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ішінен шығу…"</string> <string name="owner_name" msgid="8713560351570795743">"Құрылғы иесі"</string> <string name="error_message_title" msgid="4082495589294631966">"Қателік"</string> @@ -1832,7 +1832,7 @@ <string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін"</string> <string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін (келесі дабыл)"</string> <string name="zen_mode_forever" msgid="740585666364912448">"Өшірілгенге дейін"</string> - <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"\"Мазаламау\" режимін өшіргенше"</string> + <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Мазаламау режимін өшіргенше"</string> <string name="zen_mode_rule_name_combination" msgid="7174598364351313725">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="8009920446193610996">"Тасалау"</string> <string name="zen_mode_feature_name" msgid="3785547207263754500">"Мазаламау"</string> @@ -1987,10 +1987,10 @@ <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Қоңыраулар мен хабарландырулардың вибрациясы болады"</string> <string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"Қоңыраулар мен хабарландырулардың дыбыстық сигналы өшіріледі"</string> <string name="notification_channel_system_changes" msgid="2462010596920209678">"Жүйе өзгерістері"</string> - <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"\"Мазаламау\" режимі"</string> - <string name="zen_upgrade_notification_visd_title" msgid="2001148984371968620">"Жаңа: \"Мазаламау\" режимі хабарландыруларды жасыруда"</string> + <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Мазаламау режимі"</string> + <string name="zen_upgrade_notification_visd_title" msgid="2001148984371968620">"Жаңа: Мазаламау режимі хабарландыруларды жасыруда"</string> <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Толығырақ ақпарат алу және өзгерту үшін түртіңіз."</string> - <string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\"Мазаламау\" режимі өзгерді"</string> + <string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Мазаламау режимі өзгерді"</string> <string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Түймені түртіп, неге тыйым салынатынын көріңіз."</string> <string name="notification_app_name_system" msgid="3045196791746735601">"Жүйе"</string> <string name="notification_app_name_settings" msgid="9088548800899952531">"Параметрлер"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 6a0cd27b27a3..b826ac6a354e 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"ವೈಶಿಷ್ಟ್ಯಗಳ ನಡುವೆ ಬದಲಿಸಲು, ಮೂರು ಬೆರಳುಗಳನ್ನು ಬಳಸಿ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಮತ್ತು ಒತ್ತಿ ಹಿಡಿಯಿರಿ."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"ಹಿಗ್ಗಿಸುವಿಕೆ"</string> <string name="user_switched" msgid="7249833311585228097">"ಪ್ರಸ್ತುತ ಬಳಕೆದಾರರು <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> ಗೆ ಬದಲಾಯಿಸಲಾಗುತ್ತಿದೆ…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>ಗೆ ಬದಲಾಯಿಸಲಾಗುತ್ತಿದೆ…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ಅವರನ್ನು ಲಾಗ್ ಔಟ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string> <string name="owner_name" msgid="8713560351570795743">"ಮಾಲೀಕರು"</string> <string name="error_message_title" msgid="4082495589294631966">"ದೋಷ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 63c3a379ad66..ae2e1db07c45 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"기능 간에 전환하려면 세 손가락을 사용하여 위로 스와이프한 다음 잠시 기다립니다."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"확대"</string> <string name="user_switched" msgid="7249833311585228097">"현재 사용자는 <xliff:g id="NAME">%1$s</xliff:g>님입니다."</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>(으)로 전환하는 중…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>로 전환하는 중…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>님을 로그아웃하는 중…"</string> <string name="owner_name" msgid="8713560351570795743">"소유자"</string> <string name="error_message_title" msgid="4082495589294631966">"오류"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 66ec0e691eed..81d805512048 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"ເພື່ອສະຫຼັບລະຫວ່າງຄຸນສົມບັດຕ່າງໆ, ໃຫ້ປັດຂຶ້ນດ້ວຍສາມນິ້ວຄ້າງໄວ້."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"ການຂະຫຍາຍ"</string> <string name="user_switched" msgid="7249833311585228097">"ຜູ່ໃຊ້ປັດຈຸບັນ <xliff:g id="NAME">%1$s</xliff:g> ."</string> - <string name="user_switching_message" msgid="1912993630661332336">"ກຳລັງສະລັບໄປຫາ <xliff:g id="NAME">%1$s</xliff:g>…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"ກຳລັງສະຫຼັບໄປຫາ<xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"ກຳລັງອອກຈາກລະບົບ <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"ເຈົ້າຂອງ"</string> <string name="error_message_title" msgid="4082495589294631966">"ຜິດພາດ"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 35d39cb7a87e..f88a8e201ae2 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -20,7 +20,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="byteShort" msgid="202579285008794431">"Б"</string> + <string name="byteShort" msgid="202579285008794431">"B"</string> <string name="kilobyteShort" msgid="2214285521564195803">"KB"</string> <string name="megabyteShort" msgid="6649361267635823443">"MB"</string> <string name="gigabyteShort" msgid="7515809460261287991">"GB"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index eaadc5604a3a..a82f071c3e06 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"एउटा सुविधाबाट अर्को सुविधामा जान तीनवटा औँलाले माथितिर स्वाइप गरी स्क्रिनमा टच एण्ड होल्ड गर्नुहोस्।"</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"म्याग्निफिकेसन"</string> <string name="user_switched" msgid="7249833311585228097">"अहिलेको प्रयोगकर्ता <xliff:g id="NAME">%1$s</xliff:g>।"</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> मा स्विच गर्दै..."</string> + <string name="user_switching_message" msgid="1912993630661332336">"स्विच गरेर <xliff:g id="NAME">%1$s</xliff:g> बनाइँदै..."</string> <string name="user_logging_out_message" msgid="7216437629179710359">"लग आउट गर्दै <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"मालिक"</string> <string name="error_message_title" msgid="4082495589294631966">"त्रुटि"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 020b2890519a..40c154efc2b9 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"ଫିଚରଗୁଡ଼ିକ ମଧ୍ୟରେ ସ୍ୱିଚ୍ କରିବାକୁ, ତିନୋଟି ଆଙ୍ଗୁଠିରେ ଉପରକୁ ସ୍ୱାଇପ୍ କରି ଧରି ରଖନ୍ତୁ।"</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"ମ୍ୟାଗ୍ନିଫିକେସନ୍"</string> <string name="user_switched" msgid="7249833311585228097">"ବର୍ତ୍ତମାନର ୟୁଜର୍ ହେଉଛନ୍ତି <xliff:g id="NAME">%1$s</xliff:g>।"</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> ରେ ସୁଇଚ୍ କରନ୍ତୁ…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>ରେ ସ୍ୱିଚ୍ କରନ୍ତୁ…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ଙ୍କୁ ଲଗଆଉଟ୍ କରାଯାଉଛି…"</string> <string name="owner_name" msgid="8713560351570795743">"ମାଲିକ"</string> <string name="error_message_title" msgid="4082495589294631966">"ତ୍ରୁଟି"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 55bc8cddc27e..43409ef93197 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ, ਤਿੰਨ ਉਂਗਲਾਂ ਨਾਲ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰਕੇ ਦਬਾਈ ਰੱਖੋ।"</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"ਵੱਡਦਰਸ਼ੀਕਰਨ"</string> <string name="user_switched" msgid="7249833311585228097">"ਮੌਜੂਦਾ ਉਪਭੋਗਤਾ <xliff:g id="NAME">%1$s</xliff:g>।"</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> ਤੇ ਸਵਿਚ ਕਰ ਰਿਹਾ ਹੈ…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> \'ਤੇ ਸਵਿਚ ਕਰ ਰਿਹਾ ਹੈ…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ਨੂੰ ਲਾਗ-ਆਉਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ …"</string> <string name="owner_name" msgid="8713560351570795743">"ਮਾਲਕ"</string> <string name="error_message_title" msgid="4082495589294631966">"ਅਸ਼ੁੱਧੀ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 68dd6ec03526..3a4cc8f3379e 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -542,7 +542,7 @@ <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Zezwala aplikacji na modyfikowanie kolekcji zdjęć."</string> <string name="permlab_mediaLocation" msgid="7368098373378598066">"odczytywanie lokalizacji z kolekcji multimediów"</string> <string name="permdesc_mediaLocation" msgid="597912899423578138">"Zezwala aplikacji na odczytywanie lokalizacji z kolekcji multimediów."</string> - <string name="biometric_dialog_default_title" msgid="55026799173208210">"Potwierdź swoją tożsamość"</string> + <string name="biometric_dialog_default_title" msgid="55026799173208210">"Potwierdź, że to Ty"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Sprzęt biometryczny niedostępny"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Anulowano uwierzytelnianie"</string> <string name="biometric_not_recognized" msgid="5106687642694635888">"Nie rozpoznano"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 3f2c6dff6d63..6799541767c0 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1705,7 +1705,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Для переключения между функциями проведите по экрану снизу вверх тремя пальцами и задержите их."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Увеличение"</string> <string name="user_switched" msgid="7249833311585228097">"Выбран аккаунт пользователя <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"Смена профиля на <xliff:g id="NAME">%1$s</xliff:g>…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"Смена профиля на \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Выход из аккаунта <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"Владелец"</string> <string name="error_message_title" msgid="4082495589294631966">"Ошибка"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 89c302764fc7..1c317ef64241 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Për të kaluar mes veçorive, rrëshqit shpejt lart me tre gishta dhe mbaje prekur."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Zmadhimi"</string> <string name="user_switched" msgid="7249833311585228097">"Emri i përdoruesit aktual: <xliff:g id="NAME">%1$s</xliff:g>"</string> - <string name="user_switching_message" msgid="1912993630661332336">"Po kalon në <xliff:g id="NAME">%1$s</xliff:g>…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"Po kalon në \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> po del…"</string> <string name="owner_name" msgid="8713560351570795743">"Zotëruesi"</string> <string name="error_message_title" msgid="4082495589294631966">"Gabim"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 45a0354311f6..1e4e035b33ca 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Ili ubadilishe kati ya vipengele, telezesha vidole vitatu juu na ushikilie."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Ukuzaji"</string> <string name="user_switched" msgid="7249833311585228097">"Mtumiaji wa sasa <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"Inabadili kwenda <xliff:g id="NAME">%1$s</xliff:g>…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"Inaenda kwa <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Inamwondoa <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"Mmiliki"</string> <string name="error_message_title" msgid="4082495589294631966">"Hitilafu"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 14ca7a287952..be837b427612 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -1661,9 +1661,9 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"ఫీచర్ల మధ్య మారడానికి, మూడు చేతి వేళ్ళతో పైకి స్వైప్ చేసి పట్టుకోండి."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"మాగ్నిఫికేషన్"</string> <string name="user_switched" msgid="7249833311585228097">"ప్రస్తుత వినియోగదారు <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>కి మారుస్తోంది…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> యూజర్కు స్విచ్ అవుతోంది…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ని లాగ్ అవుట్ చేస్తోంది…"</string> - <string name="owner_name" msgid="8713560351570795743">"యజమాని"</string> + <string name="owner_name" msgid="8713560351570795743">"ఓనర్"</string> <string name="error_message_title" msgid="4082495589294631966">"ఎర్రర్"</string> <string name="error_message_change_not_allowed" msgid="843159705042381454">"ఈ మార్పును మీ నిర్వాహకులు అనుమతించలేదు"</string> <string name="app_not_found" msgid="3429506115332341800">"ఈ చర్యను నిర్వహించడానికి యాప్ ఏదీ కనుగొనబడలేదు"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 19bdba898eb9..acd04a43375b 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1663,7 +1663,7 @@ <string name="user_switched" msgid="7249833311585228097">"Geçerli kullanıcı: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> adlı kullanıcıya geçiliyor…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> hesabından çıkış yapılıyor…"</string> - <string name="owner_name" msgid="8713560351570795743">"Sahibi"</string> + <string name="owner_name" msgid="8713560351570795743">"Sahip"</string> <string name="error_message_title" msgid="4082495589294631966">"Hata"</string> <string name="error_message_change_not_allowed" msgid="843159705042381454">"Yöneticiniz bu değişikliğe izin vermiyor"</string> <string name="app_not_found" msgid="3429506115332341800">"Bu eylemi gerçekleştirecek bir uygulama bulunamadı"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 1f00a5dc8137..fa3af33dd8f9 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1705,7 +1705,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Щоб переключитися між функціями, проведіть по екрану знизу вгору трьома пальцями й утримуйте їх."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Збільшення"</string> <string name="user_switched" msgid="7249833311585228097">"Поточний користувач: <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"Перехід в обліковий запис \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"Перехід у режим \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Вихід з облікового запису користувача <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="owner_name" msgid="8713560351570795743">"Власник"</string> <string name="error_message_title" msgid="4082495589294631966">"Помилка"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index a64431ce351a..96fea7c58b07 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1661,7 +1661,7 @@ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Funksiyalarni almashtirish uchun uchta barmoq bilan tepaga suring va ushlab turing."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Kattalashtirish"</string> <string name="user_switched" msgid="7249833311585228097">"Joriy foydalanuvchi <xliff:g id="NAME">%1$s</xliff:g>."</string> - <string name="user_switching_message" msgid="1912993630661332336">"Quyidagi foydalanuvchiga o‘tilmoqda: <xliff:g id="NAME">%1$s</xliff:g>…"</string> + <string name="user_switching_message" msgid="1912993630661332336">"Bunga almashilmoqda: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> hisobidan chiqilmoqda…"</string> <string name="owner_name" msgid="8713560351570795743">"Egasi"</string> <string name="error_message_title" msgid="4082495589294631966">"Xato"</string> diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 526c1ca8e4a6..f3188450e94b 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -6316,6 +6316,25 @@ public class ConnectivityService extends IConnectivityManager.Stub return newNc; } + private void updateNetworkInfoForRoamingAndSuspended(NetworkAgentInfo nai, + NetworkCapabilities prevNc, NetworkCapabilities newNc) { + final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); + final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); + final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + if (prevSuspended != suspended) { + // TODO (b/73132094) : remove this call once the few users of onSuspended and + // onResumed have been removed. + notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED + : ConnectivityManager.CALLBACK_RESUMED); + } + if (prevSuspended != suspended || prevRoaming != roaming) { + // updateNetworkInfo will mix in the suspended info from the capabilities and + // take appropriate action for the network having possibly changed state. + updateNetworkInfo(nai, nai.networkInfo); + } + } + /** * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically: * @@ -6347,25 +6366,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // on this network. We might have been called by rematchNetworkAndRequests when a // network changed foreground state. processListenRequests(nai); - final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); - final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); - final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); - final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); - if (prevSuspended != suspended || prevRoaming != roaming) { - // TODO (b/73132094) : remove this call once the few users of onSuspended and - // onResumed have been removed. - notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED - : ConnectivityManager.CALLBACK_RESUMED); - // updateNetworkInfo will mix in the suspended info from the capabilities and - // take appropriate action for the network having possibly changed state. - updateNetworkInfo(nai, nai.networkInfo); - } } else { // If the requestable capabilities have changed or the score changed, we can't have been // called by rematchNetworkAndRequests, so it's safe to start a rematch. rematchAllNetworksAndRequests(); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } + updateNetworkInfoForRoamingAndSuspended(nai, prevNc, newNc); // TODO : static analysis indicates that prevNc can't be null here (getAndSetNetworkCaps // never returns null), so mark the relevant members and functions in nai as @NonNull and diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index dbb91a58369d..af4df1a0f8d1 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -194,7 +194,8 @@ public class Vpn { @VisibleForTesting protected String mPackage; private int mOwnerUID; private boolean mIsPackageTargetingAtLeastQ; - private String mInterface; + @VisibleForTesting + protected String mInterface; private Connection mConnection; /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */ diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index c4801aab5c32..344d0c3260c9 100644 --- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -195,7 +195,8 @@ class ConnectivityServiceIntegrationTest { "https://secure.test.android.com", responseCode = 204, contentLength = 42, redirectUrl = null)) - val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context) + val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), null /* ncTemplate */, + context) networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS) na.addCapability(NET_CAPABILITY_INTERNET) @@ -204,4 +205,4 @@ class ConnectivityServiceIntegrationTest { testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS) assertEquals(2, nsInstrumentation.getRequestUrls().size) } -}
\ No newline at end of file +} diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index 9f0b41fa0cdf..05cd12b619fc 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -69,12 +69,12 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE; private Integer mExpectedKeepaliveSlot = null; - public NetworkAgentWrapper(int transport, LinkProperties linkProperties, Context context) - throws Exception { + public NetworkAgentWrapper(int transport, LinkProperties linkProperties, + NetworkCapabilities ncTemplate, Context context) throws Exception { final int type = transportToLegacyType(transport); final String typeName = ConnectivityManager.getNetworkTypeName(type); mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock"); - mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities(); mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED); mNetworkCapabilities.addTransportType(transport); switch (transport) { diff --git a/tests/net/integration/util/com/android/server/TestNetIdManager.kt b/tests/net/integration/util/com/android/server/TestNetIdManager.kt index eb290dc7d24a..938a694e8ba9 100644 --- a/tests/net/integration/util/com/android/server/TestNetIdManager.kt +++ b/tests/net/integration/util/com/android/server/TestNetIdManager.kt @@ -35,4 +35,5 @@ class TestNetIdManager : NetIdManager() { private val nextId = AtomicInteger(MAX_NET_ID) override fun reserveNetId() = nextId.decrementAndGet() override fun releaseNetId(id: Int) = Unit + fun peekNextNetId() = nextId.get() - 1 } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 385005f90c3b..a5554c740e7f 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -36,6 +36,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; +import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK; @@ -170,6 +171,7 @@ import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; @@ -274,13 +276,16 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.function.Supplier; @@ -319,6 +324,7 @@ public class ConnectivityServiceTest { private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; private static final String WIFI_WOL_IFNAME = "test_wlan_wol"; + private static final String VPN_IFNAME = "tun10042"; private static final String TEST_PACKAGE_NAME = "com.android.test.package"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; @@ -336,6 +342,7 @@ public class ConnectivityServiceTest { private INetworkPolicyListener mPolicyListener; private WrappedMultinetworkPolicyTracker mPolicyTracker; private HandlerThread mAlarmManagerThread; + private TestNetIdManager mNetIdManager; @Mock IIpConnectivityMetrics mIpConnectivityMetrics; @Mock IpConnectivityMetrics.Logger mMetricsService; @@ -382,9 +389,6 @@ public class ConnectivityServiceTest { private class MockContext extends BroadcastInterceptingContext { private final MockContentResolver mContentResolver; - // Contains all registered receivers since this object was created. Useful to clear - // them when needed, as BroadcastInterceptingContext does not provide this facility. - private final List<BroadcastReceiver> mRegisteredReceivers = new ArrayList<>(); @Spy private Resources mResources; private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); @@ -502,19 +506,6 @@ public class ConnectivityServiceTest { public void setPermission(String permission, Integer granted) { mMockedPermissions.put(permission, granted); } - - @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { - mRegisteredReceivers.add(receiver); - return super.registerReceiver(receiver, filter); - } - - public void clearRegisteredReceivers() { - // super.unregisterReceiver is a no-op for receivers that are not registered (because - // they haven't been registered or because they have already been unregistered). - // For the same reason, don't bother clearing mRegisteredReceivers. - for (final BroadcastReceiver rcv : mRegisteredReceivers) unregisterReceiver(rcv); - } } private void waitForIdle() { @@ -543,10 +534,10 @@ public class ConnectivityServiceTest { } // Bring up a network that we can use to send messages to ConnectivityService. - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); Network n = mWiFiNetworkAgent.getNetwork(); assertNotNull(n); @@ -563,10 +554,10 @@ public class ConnectivityServiceTest { @Ignore public void verifyThatNotWaitingForIdleCausesRaceConditions() throws Exception { // Bring up a network that we can use to send messages to ConnectivityService. - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); Network n = mWiFiNetworkAgent.getNetwork(); assertNotNull(n); @@ -604,12 +595,17 @@ public class ConnectivityServiceTest { private String mRedirectUrl; TestNetworkAgentWrapper(int transport) throws Exception { - this(transport, new LinkProperties()); + this(transport, new LinkProperties(), null); } TestNetworkAgentWrapper(int transport, LinkProperties linkProperties) throws Exception { - super(transport, linkProperties, mServiceContext); + this(transport, linkProperties, null); + } + + private TestNetworkAgentWrapper(int transport, LinkProperties linkProperties, + NetworkCapabilities ncTemplate) throws Exception { + super(transport, linkProperties, ncTemplate, mServiceContext); // Waits for the NetworkAgent to be registered, which includes the creation of the // NetworkMonitor. @@ -1004,46 +1000,36 @@ public class ConnectivityServiceTest { } } + private Set<UidRange> uidRangesForUid(int uid) { + final ArraySet<UidRange> ranges = new ArraySet<>(); + ranges.add(new UidRange(uid, uid)); + return ranges; + } + private static Looper startHandlerThreadAndReturnLooper() { final HandlerThread handlerThread = new HandlerThread("MockVpnThread"); handlerThread.start(); return handlerThread.getLooper(); } - private class MockVpn extends Vpn { - // TODO : the interactions between this mock and the mock network agent are too - // hard to get right at this moment, because it's unclear in which case which - // target needs to get a method call or both, and in what order. It's because - // MockNetworkAgent wants to manage its own NetworkCapabilities, but the Vpn - // parent class of MockVpn agent wants that responsibility. - // That being said inside the test it should be possible to make the interactions - // harder to get wrong with precise speccing, judicious comments, helper methods - // and a few sprinkled assertions. - - private boolean mConnected = false; + private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork { // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does // not inherit from NetworkAgent. private TestNetworkAgentWrapper mMockNetworkAgent; - private int mVpnType = VpnManager.TYPE_VPN_SERVICE; + private boolean mAgentRegistered = false; + private int mVpnType = VpnManager.TYPE_VPN_SERVICE; private VpnInfo mVpnInfo; - private Network[] mUnderlyingNetworks; public MockVpn(int userId) { super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService, userId, mock(KeyStore.class)); - } - - public void setNetworkAgent(TestNetworkAgentWrapper agent) { - agent.waitForIdle(TIMEOUT_MS); - mMockNetworkAgent = agent; - mNetworkAgent = agent.getNetworkAgent(); - mNetworkCapabilities.set(agent.getNetworkCapabilities()); + mConfig = new VpnConfig(); } public void setUids(Set<UidRange> uids) { mNetworkCapabilities.setUids(uids); - updateCapabilities(null /* defaultNetwork */); + updateCapabilitiesInternal(null /* defaultNetwork */, true); } public void setVpnType(int vpnType) { @@ -1051,21 +1037,13 @@ public class ConnectivityServiceTest { } @Override - public int getNetId() { - if (mMockNetworkAgent == null) { - return NETID_UNSET; - } - return mMockNetworkAgent.getNetwork().netId; - } - - @Override - public boolean appliesToUid(int uid) { - return mConnected; // Trickery to simplify testing. + public Network getNetwork() { + return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork(); } @Override - protected boolean isCallerEstablishedOwnerLocked() { - return mConnected; // Similar trickery + public int getNetId() { + return (mMockNetworkAgent == null) ? NETID_UNSET : mMockNetworkAgent.getNetwork().netId; } @Override @@ -1073,41 +1051,94 @@ public class ConnectivityServiceTest { return mVpnType; } - private void connect(boolean isAlwaysMetered) { - mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); - mConnected = true; - mConfig = new VpnConfig(); + private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp) + throws Exception { + if (mAgentRegistered) throw new IllegalStateException("already registered"); + setUids(uids); mConfig.isMetered = isAlwaysMetered; + mInterface = VPN_IFNAME; + mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp, + mNetworkCapabilities); + mMockNetworkAgent.waitForIdle(TIMEOUT_MS); + mAgentRegistered = true; + mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); + mNetworkAgent = mMockNetworkAgent.getNetworkAgent(); } - public void connectAsAlwaysMetered() { - connect(true /* isAlwaysMetered */); + private void registerAgent(Set<UidRange> uids) throws Exception { + registerAgent(false /* isAlwaysMetered */, uids, new LinkProperties()); } - public void connect() { - connect(false /* isAlwaysMetered */); + private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) { + mMockNetworkAgent.connect(validated, hasInternet, isStrictMode); } - @Override - public NetworkCapabilities updateCapabilities(Network defaultNetwork) { - if (!mConnected) return null; + private void connect(boolean validated) { + mMockNetworkAgent.connect(validated); + } + + private TestNetworkAgentWrapper getAgent() { + return mMockNetworkAgent; + } + + public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated, + boolean hasInternet, boolean isStrictMode) throws Exception { + mNetworkCapabilities.setOwnerUid(uid); + mNetworkCapabilities.setAdministratorUids(new int[]{uid}); + registerAgent(false, ranges, lp); + connect(validated, hasInternet, isStrictMode); + waitForIdle(); + } + + public void establish(LinkProperties lp, int uid, Set<UidRange> ranges) throws Exception { + establish(lp, uid, ranges, true, true, false); + } + + public void establishForMyUid(LinkProperties lp) throws Exception { + final int uid = Process.myUid(); + establish(lp, uid, uidRangesForUid(uid), true, true, false); + } + + public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode) + throws Exception { + final int uid = Process.myUid(); + establish(new LinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet, + isStrictMode); + } + + public void establishForMyUid() throws Exception { + establishForMyUid(new LinkProperties()); + } + + public void sendLinkProperties(LinkProperties lp) { + mMockNetworkAgent.sendLinkProperties(lp); + } + + private NetworkCapabilities updateCapabilitiesInternal(Network defaultNetwork, + boolean sendToConnectivityService) { + if (!mAgentRegistered) return null; super.updateCapabilities(defaultNetwork); // Because super.updateCapabilities will update the capabilities of the agent but // not the mock agent, the mock agent needs to know about them. - copyCapabilitiesToNetworkAgent(); + copyCapabilitiesToNetworkAgent(sendToConnectivityService); return new NetworkCapabilities(mNetworkCapabilities); } - private void copyCapabilitiesToNetworkAgent() { + private void copyCapabilitiesToNetworkAgent(boolean sendToConnectivityService) { if (null != mMockNetworkAgent) { mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities, - false /* sendToConnectivityService */); + sendToConnectivityService); } } + @Override + public NetworkCapabilities updateCapabilities(Network defaultNetwork) { + return updateCapabilitiesInternal(defaultNetwork, false); + } + public void disconnect() { - mConnected = false; - mConfig = null; + if (mMockNetworkAgent != null) mMockNetworkAgent.disconnect(); + mAgentRegistered = false; } @Override @@ -1120,18 +1151,6 @@ public class ConnectivityServiceTest { private synchronized void setVpnInfo(VpnInfo vpnInfo) { mVpnInfo = vpnInfo; } - - @Override - public synchronized Network[] getUnderlyingNetworks() { - if (mUnderlyingNetworks != null) return mUnderlyingNetworks; - - return super.getUnderlyingNetworks(); - } - - /** Don't override behavior for {@link Vpn#setUnderlyingNetworks}. */ - private synchronized void overrideUnderlyingNetworks(Network[] underlyingNetworks) { - mUnderlyingNetworks = underlyingNetworks; - } } private void mockVpn(int uid) { @@ -1194,6 +1213,8 @@ public class ConnectivityServiceTest { @Before public void setUp() throws Exception { + mNetIdManager = new TestNetIdManager(); + mContext = InstrumentationRegistry.getContext(); MockitoAnnotations.initMocks(this); @@ -1264,7 +1285,7 @@ public class ConnectivityServiceTest { final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class); doReturn(mCsHandlerThread).when(deps).makeHandlerThread(); - doReturn(new TestNetIdManager()).when(deps).makeNetIdManager(); + doReturn(mNetIdManager).when(deps).makeNetIdManager(); doReturn(mNetworkStack).when(deps).getNetworkStack(); doReturn(systemProperties).when(deps).getSystemProperties(); doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); @@ -1323,6 +1344,9 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.disconnect(); mEthernetNetworkAgent = null; } + mMockVpn.disconnect(); + waitForIdle(); + FakeSettingsProvider.clearSettingsProvider(); mCsHandlerThread.quitSafely(); @@ -1387,29 +1411,79 @@ public class ConnectivityServiceTest { } /** - * Return a ConditionVariable that opens when {@code count} numbers of CONNECTIVITY_ACTION - * broadcasts are received. + * Class to simplify expecting broadcasts using BroadcastInterceptingContext. + * Ensures that the receiver is unregistered after the expected broadcast is received. This + * cannot be done in the BroadcastReceiver itself because BroadcastInterceptingContext runs + * the receivers' receive method while iterating over the list of receivers, and unregistering + * the receiver during iteration throws ConcurrentModificationException. */ - private ConditionVariable registerConnectivityBroadcast(final int count) { + private class ExpectedBroadcast extends CompletableFuture<Intent> { + private final BroadcastReceiver mReceiver; + + ExpectedBroadcast(BroadcastReceiver receiver) { + mReceiver = receiver; + } + + public Intent expectBroadcast(int timeoutMs) throws Exception { + try { + return get(timeoutMs, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + fail("Expected broadcast not received after " + timeoutMs + " ms"); + return null; + } finally { + mServiceContext.unregisterReceiver(mReceiver); + } + } + + public Intent expectBroadcast() throws Exception { + return expectBroadcast(TIMEOUT_MS); + } + + public void expectNoBroadcast(int timeoutMs) throws Exception { + waitForIdle(); + try { + final Intent intent = get(timeoutMs, TimeUnit.MILLISECONDS); + fail("Unexpected broadcast: " + intent.getAction()); + } catch (TimeoutException expected) { + } finally { + mServiceContext.unregisterReceiver(mReceiver); + } + } + } + + /** Expects that {@code count} CONNECTIVITY_ACTION broadcasts are received. */ + private ExpectedBroadcast registerConnectivityBroadcast(final int count) { return registerConnectivityBroadcastThat(count, intent -> true); } - private ConditionVariable registerConnectivityBroadcastThat(final int count, + private ExpectedBroadcast registerConnectivityBroadcastThat(final int count, @NonNull final Predicate<Intent> filter) { - final ConditionVariable cv = new ConditionVariable(); final IntentFilter intentFilter = new IntentFilter(CONNECTIVITY_ACTION); + // AtomicReference allows receiver to access expected even though it is constructed later. + final AtomicReference<ExpectedBroadcast> expectedRef = new AtomicReference<>(); final BroadcastReceiver receiver = new BroadcastReceiver() { - private int remaining = count; - public void onReceive(Context context, Intent intent) { - if (!filter.test(intent)) return; - if (--remaining == 0) { - cv.open(); - mServiceContext.unregisterReceiver(this); - } - } - }; + private int mRemaining = count; + public void onReceive(Context context, Intent intent) { + final int type = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1); + final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO); + Log.d(TAG, "Received CONNECTIVITY_ACTION type=" + type + " ni=" + ni); + if (!filter.test(intent)) return; + if (--mRemaining == 0) { + expectedRef.get().complete(intent); + } + } + }; + final ExpectedBroadcast expected = new ExpectedBroadcast(receiver); + expectedRef.set(expected); mServiceContext.registerReceiver(receiver, intentFilter); - return cv; + return expected; + } + + private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) { + return registerConnectivityBroadcastThat(1, intent -> + type == intent.getIntExtra(EXTRA_NETWORK_TYPE, -1) && state.equals( + ((NetworkInfo) intent.getParcelableExtra(EXTRA_NETWORK_INFO)) + .getDetailedState())); } @Test @@ -1433,10 +1507,9 @@ public class ConnectivityServiceTest { // Connect the cell agent and wait for the connected broadcast. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_SUPL); - final ConditionVariable cv1 = registerConnectivityBroadcastThat(1, - intent -> intent.getIntExtra(EXTRA_NETWORK_TYPE, -1) == TYPE_MOBILE); + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); mCellNetworkAgent.connect(true); - waitFor(cv1); + b.expectBroadcast(); // Build legacy request for SUPL. final NetworkCapabilities legacyCaps = new NetworkCapabilities(); @@ -1446,27 +1519,17 @@ public class ConnectivityServiceTest { ConnectivityManager.REQUEST_ID_UNSET, NetworkRequest.Type.REQUEST); // File request, withdraw it and make sure no broadcast is sent - final ConditionVariable cv2 = registerConnectivityBroadcast(1); + b = registerConnectivityBroadcast(1); final TestNetworkCallback callback = new TestNetworkCallback(); mCm.requestNetwork(legacyRequest, callback); callback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); mCm.unregisterNetworkCallback(callback); - assertFalse(cv2.block(800)); // 800ms long enough to at least flake if this is sent - // As the broadcast did not fire, the receiver was not unregistered. Do this now. - mServiceContext.clearRegisteredReceivers(); - - // Disconnect the network and expect mobile disconnected broadcast. Use a small hack to - // check that has been sent. - final AtomicBoolean vanillaAction = new AtomicBoolean(false); - final ConditionVariable cv3 = registerConnectivityBroadcastThat(1, intent -> { - if (intent.getAction().equals(CONNECTIVITY_ACTION)) { - vanillaAction.set(true); - } - return !((NetworkInfo) intent.getExtra(EXTRA_NETWORK_INFO, -1)).isConnected(); - }); + b.expectNoBroadcast(800); // 800ms long enough to at least flake if this is sent + + // Disconnect the network and expect mobile disconnected broadcast. + b = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); mCellNetworkAgent.disconnect(); - waitFor(cv3); - assertTrue(vanillaAction.get()); + b.expectBroadcast(); } @Test @@ -1477,9 +1540,9 @@ public class ConnectivityServiceTest { assertNull(mCm.getActiveNetworkInfo()); assertNull(mCm.getActiveNetwork()); // Test bringing up validated cellular. - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); assertLength(2, mCm.getAllNetworks()); assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || @@ -1487,9 +1550,9 @@ public class ConnectivityServiceTest { assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) || mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork())); // Test bringing up validated WiFi. - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); assertLength(2, mCm.getAllNetworks()); assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || @@ -1504,9 +1567,9 @@ public class ConnectivityServiceTest { assertLength(1, mCm.getAllNetworks()); assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork()); // Test WiFi disconnect. - cv = registerConnectivityBroadcast(1); + b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyNoNetwork(); } @@ -1514,9 +1577,9 @@ public class ConnectivityServiceTest { public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception { // Test bringing up unvalidated WiFi mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up unvalidated cellular mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); @@ -1529,19 +1592,19 @@ public class ConnectivityServiceTest { verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test cellular disconnect. - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi disconnect. - cv = registerConnectivityBroadcast(1); + b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyNoNetwork(); } @@ -1549,25 +1612,25 @@ public class ConnectivityServiceTest { public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception { // Test bringing up unvalidated cellular. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mCellNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up unvalidated WiFi. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi disconnect. - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test cellular disconnect. - cv = registerConnectivityBroadcast(1); + b = registerConnectivityBroadcast(1); mCellNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyNoNetwork(); } @@ -1575,24 +1638,24 @@ public class ConnectivityServiceTest { public void testUnlingeringDoesNotValidate() throws Exception { // Test bringing up unvalidated WiFi. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test bringing up validated cellular. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); // Test cellular disconnect. - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Unlingering a network should not cause it to be marked as validated. assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( @@ -1603,25 +1666,25 @@ public class ConnectivityServiceTest { public void testCellularOutscoresWeakWifi() throws Exception { // Test bringing up validated cellular. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up validated WiFi. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test WiFi getting really weak. - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.adjustScore(-11); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test WiFi restoring signal strength. - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.adjustScore(11); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); } @@ -1639,9 +1702,9 @@ public class ConnectivityServiceTest { mCellNetworkAgent.expectDisconnected(); // Test bringing up validated WiFi. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - final ConditionVariable cv = registerConnectivityBroadcast(1); + final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up unvalidated cellular. // Expect it to be torn down because it could never be the highest scoring network @@ -1658,33 +1721,33 @@ public class ConnectivityServiceTest { public void testCellularFallback() throws Exception { // Test bringing up validated cellular. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test bringing up validated WiFi. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Reevaluate WiFi (it'll instantly fail DNS). - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork()); // Should quickly fall back to Cellular. - waitFor(cv); + b.expectBroadcast(); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. - waitFor(cv); + b.expectBroadcast(); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( @@ -1696,23 +1759,23 @@ public class ConnectivityServiceTest { public void testWiFiFallback() throws Exception { // Test bringing up unvalidated WiFi. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mWiFiNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Reevaluate cellular (it'll instantly fail DNS). - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); // Should quickly fall back to WiFi. - waitFor(cv); + b.expectBroadcast(); assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( NET_CAPABILITY_VALIDATED)); verifyActiveNetwork(TRANSPORT_WIFI); @@ -1782,13 +1845,13 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); // Test unvalidated networks - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = registerConnectivityBroadcast(1); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - waitFor(cv); + b.expectBroadcast(); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); // This should not trigger spurious onAvailable() callbacks, b/21762680. @@ -1797,28 +1860,28 @@ public class ConnectivityServiceTest { assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - waitFor(cv); + b.expectBroadcast(); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); - cv = registerConnectivityBroadcast(2); + b = registerConnectivityBroadcast(2); mWiFiNetworkAgent.disconnect(); genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); cellNetworkCallback.assertNoCallback(); - waitFor(cv); + b.expectBroadcast(); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); - cv = registerConnectivityBroadcast(1); + b = registerConnectivityBroadcast(1); mCellNetworkAgent.disconnect(); genericNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); - waitFor(cv); + b.expectBroadcast(); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); // Test validated networks @@ -2507,9 +2570,9 @@ public class ConnectivityServiceTest { // Test bringing up validated WiFi. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - final ConditionVariable cv = registerConnectivityBroadcast(1); + final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); // Register MMS NetworkRequest @@ -2535,9 +2598,9 @@ public class ConnectivityServiceTest { public void testMMSonCell() throws Exception { // Test bringing up cellular without MMS mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); mCellNetworkAgent.connect(false); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_CELLULAR); // Register MMS NetworkRequest @@ -3198,20 +3261,12 @@ public class ConnectivityServiceTest { waitForIdle(); assertEquals(null, mCm.getActiveNetwork()); - final int uid = Process.myUid(); - final TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true); - mMockVpn.connect(); - defaultNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); + mMockVpn.establishForMyUid(); + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - vpnNetworkAgent.disconnect(); - defaultNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent); + mMockVpn.disconnect(); + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); waitForIdle(); assertEquals(null, mCm.getActiveNetwork()); } @@ -4010,9 +4065,9 @@ public class ConnectivityServiceTest { } mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mWiFiNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); verifyActiveNetwork(TRANSPORT_WIFI); mWiFiNetworkAgent.sendLinkProperties(lp); waitForIdle(); @@ -4543,10 +4598,10 @@ public class ConnectivityServiceTest { assertNotPinnedToWifi(); // Disconnect cell and wifi. - ConditionVariable cv = registerConnectivityBroadcast(3); // cell down, wifi up, wifi down. + ExpectedBroadcast b = registerConnectivityBroadcast(3); // cell down, wifi up, wifi down. mCellNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); // Pinning takes effect even if the pinned network is the default when the pin is set... TestNetworkPinner.pin(mServiceContext, wifiRequest); @@ -4556,10 +4611,10 @@ public class ConnectivityServiceTest { assertPinnedToWifiWithWifiDefault(); // ... and is maintained even when that network is no longer the default. - cv = registerConnectivityBroadcast(1); + b = registerConnectivityBroadcast(1); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mCellNetworkAgent.connect(true); - waitFor(cv); + b.expectBroadcast(); assertPinnedToWifiWithCellDefault(); } @@ -4657,7 +4712,7 @@ public class ConnectivityServiceTest { @Test public void testNetworkInfoOfTypeNone() throws Exception { - ConditionVariable broadcastCV = registerConnectivityBroadcast(1); + ExpectedBroadcast b = registerConnectivityBroadcast(1); verifyNoNetwork(); TestNetworkAgentWrapper wifiAware = new TestNetworkAgentWrapper(TRANSPORT_WIFI_AWARE); @@ -4690,9 +4745,7 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(callback); verifyNoNetwork(); - if (broadcastCV.block(10)) { - fail("expected no broadcast, but got CONNECTIVITY_ACTION broadcast"); - } + b.expectNoBroadcast(10); } @Test @@ -4763,13 +4816,52 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(networkCallback); } + private <T> void assertSameElementsNoDuplicates(T[] expected, T[] actual) { + // Easier to implement than a proper "assertSameElements" method that also correctly deals + // with duplicates. + final String msg = Arrays.toString(expected) + " != " + Arrays.toString(actual); + assertEquals(msg, expected.length, actual.length); + Set expectedSet = new ArraySet<>(Arrays.asList(expected)); + assertEquals("expected contains duplicates", expectedSet.size(), expected.length); + // actual cannot have duplicates because it's the same length and has the same elements. + Set actualSet = new ArraySet<>(Arrays.asList(actual)); + assertEquals(expectedSet, actualSet); + } + + private void expectForceUpdateIfaces(Network[] networks, String defaultIface, + Integer vpnUid, String vpnIfname, String[] underlyingIfaces) throws Exception { + ArgumentCaptor<Network[]> networksCaptor = ArgumentCaptor.forClass(Network[].class); + ArgumentCaptor<VpnInfo[]> vpnInfosCaptor = ArgumentCaptor.forClass(VpnInfo[].class); + + verify(mStatsService, atLeastOnce()).forceUpdateIfaces(networksCaptor.capture(), + any(NetworkState[].class), eq(defaultIface), vpnInfosCaptor.capture()); + + assertSameElementsNoDuplicates(networksCaptor.getValue(), networks); + + VpnInfo[] infos = vpnInfosCaptor.getValue(); + if (vpnUid != null) { + assertEquals("Should have exactly one VPN:", 1, infos.length); + VpnInfo info = infos[0]; + assertEquals("Unexpected VPN owner:", (int) vpnUid, info.ownerUid); + assertEquals("Unexpected VPN interface:", vpnIfname, info.vpnIface); + assertSameElementsNoDuplicates(underlyingIfaces, info.underlyingIfaces); + } else { + assertEquals(0, infos.length); + return; + } + } + + private void expectForceUpdateIfaces(Network[] networks, String defaultIface) throws Exception { + expectForceUpdateIfaces(networks, defaultIface, null, null, new String[0]); + } + @Test public void testStatsIfacesChanged() throws Exception { mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()}; - Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()}; + final Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()}; + final Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()}; LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); @@ -4780,9 +4872,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false); mCellNetworkAgent.sendLinkProperties(cellLp); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME), - eq(new VpnInfo[0])); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); reset(mStatsService); // Default network switch should update ifaces. @@ -4790,32 +4880,24 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.sendLinkProperties(wifiLp); waitForIdle(); assertEquals(wifiLp, mService.getActiveLinkProperties()); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces(eq(onlyWifi), any(NetworkState[].class), eq(WIFI_IFNAME), - eq(new VpnInfo[0])); + expectForceUpdateIfaces(onlyWifi, WIFI_IFNAME); reset(mStatsService); // Disconnect should update ifaces. mWiFiNetworkAgent.disconnect(); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), - eq(MOBILE_IFNAME), eq(new VpnInfo[0])); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); reset(mStatsService); // Metered change should update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME), - eq(new VpnInfo[0])); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); reset(mStatsService); mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME), - eq(new VpnInfo[0])); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); reset(mStatsService); // Captive portal change shouldn't update ifaces @@ -4829,9 +4911,123 @@ public class ConnectivityServiceTest { // Roaming change should update ifaces mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); waitForIdle(); - verify(mStatsService, atLeastOnce()) - .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME), - eq(new VpnInfo[0])); + expectForceUpdateIfaces(onlyCell, MOBILE_IFNAME); + reset(mStatsService); + + // Test VPNs. + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(VPN_IFNAME); + + mMockVpn.establishForMyUid(lp); + + final Network[] cellAndVpn = new Network[] { + mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; + + // A VPN with default (null) underlying networks sets the underlying network's interfaces... + expectForceUpdateIfaces(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME}); + + // ...and updates them as the default network switches. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + final Network[] onlyNull = new Network[]{null}; + final Network[] wifiAndVpn = new Network[] { + mWiFiNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; + final Network[] cellAndWifi = new Network[] { + mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork()}; + final Network[] cellNullAndWifi = new Network[] { + mCellNetworkAgent.getNetwork(), null, mWiFiNetworkAgent.getNetwork()}; + + waitForIdle(); + assertEquals(wifiLp, mService.getActiveLinkProperties()); + expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{WIFI_IFNAME}); + reset(mStatsService); + + // A VPN that sets its underlying networks passes the underlying interfaces, and influences + // the default interface sent to NetworkStatsService by virtue of applying to the system + // server UID (or, in this test, to the test's UID). This is the reason for sending + // MOBILE_IFNAME even though the default network is wifi. + // TODO: fix this to pass in the actual default network interface. Whether or not the VPN + // applies to the system server UID should not have any bearing on network stats. + mService.setUnderlyingNetworksForVpn(onlyCell); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME}); + reset(mStatsService); + + mService.setUnderlyingNetworksForVpn(cellAndWifi); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + reset(mStatsService); + + // Null underlying networks are ignored. + mService.setUnderlyingNetworksForVpn(cellNullAndWifi); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + reset(mStatsService); + + // If an underlying network disconnects, that interface should no longer be underlying. + // This doesn't actually work because disconnectAndDestroyNetwork only notifies + // NetworkStatsService before the underlying network is actually removed. So the underlying + // network will only be removed if notifyIfacesChangedForNetworkStats is called again. This + // could result in incorrect data usage measurements if the interface used by the + // disconnected network is reused by a system component that does not register an agent for + // it (e.g., tethering). + mCellNetworkAgent.disconnect(); + waitForIdle(); + assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork())); + expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + + // Confirm that we never tell NetworkStatsService that cell is no longer the underlying + // network for the VPN... + verify(mStatsService, never()).forceUpdateIfaces(any(Network[].class), + any(NetworkState[].class), any() /* anyString() doesn't match null */, + argThat(infos -> infos[0].underlyingIfaces.length == 1 + && WIFI_IFNAME.equals(infos[0].underlyingIfaces[0]))); + verifyNoMoreInteractions(mStatsService); + reset(mStatsService); + + // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be + // called again, it does. For example, connect Ethernet, but with a low score, such that it + // does not become the default network. + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + mEthernetNetworkAgent.adjustScore(-40); + mEthernetNetworkAgent.connect(false); + waitForIdle(); + verify(mStatsService).forceUpdateIfaces(any(Network[].class), + any(NetworkState[].class), any() /* anyString() doesn't match null */, + argThat(vpnInfos -> vpnInfos[0].underlyingIfaces.length == 1 + && WIFI_IFNAME.equals(vpnInfos[0].underlyingIfaces[0]))); + mEthernetNetworkAgent.disconnect(); + waitForIdle(); + reset(mStatsService); + + // When a VPN declares no underlying networks (i.e., no connectivity), getAllVpnInfo + // does not return the VPN, so CS does not pass it to NetworkStatsService. This causes + // NetworkStatsFactory#adjustForTunAnd464Xlat not to attempt any VPN data migration, which + // is probably a performance improvement (though it's very unlikely that a VPN would declare + // no underlying networks). + // Also, for the same reason as above, the active interface passed in is null. + mService.setUnderlyingNetworksForVpn(new Network[0]); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, null); + reset(mStatsService); + + // Specifying only a null underlying network is the same as no networks. + mService.setUnderlyingNetworksForVpn(onlyNull); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, null); + reset(mStatsService); + + // Specifying networks that are all disconnected is the same as specifying no networks. + mService.setUnderlyingNetworksForVpn(onlyCell); + waitForIdle(); + expectForceUpdateIfaces(wifiAndVpn, null); reset(mStatsService); } @@ -5187,6 +5383,176 @@ public class ConnectivityServiceTest { } @Test + public void testVpnConnectDisconnectUnderlyingNetwork() throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + final NetworkRequest request = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN).build(); + + mCm.registerNetworkCallback(request, callback); + + // Bring up a VPN that specifies an underlying network that does not exist yet. + // Note: it's sort of meaningless for a VPN app to declare a network that doesn't exist yet, + // (and doing so is difficult without using reflection) but it's good to test that the code + // behaves approximately correctly. + mMockVpn.establishForMyUid(false, true, false); + final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId()); + mService.setUnderlyingNetworksForVpn(new Network[]{wifiNetwork}); + callback.expectAvailableCallbacksUnvalidated(mMockVpn); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_VPN)); + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_WIFI)); + + // Make that underlying network connect, and expect to see its capabilities immediately + // reflected in the VPN's capabilities. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + assertEquals(wifiNetwork, mWiFiNetworkAgent.getNetwork()); + mWiFiNetworkAgent.connect(false); + // TODO: the callback for the VPN happens before any callbacks are called for the wifi + // network that has just connected. There appear to be two issues here: + // 1. The VPN code will accept an underlying network as soon as getNetworkCapabilities() for + // it returns non-null (which happens very early, during handleRegisterNetworkAgent). + // This is not correct because that that point the network is not connected and cannot + // pass any traffic. + // 2. When a network connects, updateNetworkInfo propagates underlying network capabilities + // before rematching networks. + // Given that this scenario can't really happen, this is probably fine for now. + callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_VPN)); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_WIFI)); + + // Disconnect the network, and expect to see the VPN capabilities change accordingly. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + callback.expectCapabilitiesThat(mMockVpn, (nc) -> + nc.getTransportTypes().length == 1 && nc.hasTransport(TRANSPORT_VPN)); + + mMockVpn.disconnect(); + mCm.unregisterNetworkCallback(callback); + } + + private void assertGetNetworkInfoOfGetActiveNetworkIsConnected(boolean expectedConnectivity) { + // What Chromium used to do before https://chromium-review.googlesource.com/2605304 + assertEquals("Unexpected result for getActiveNetworkInfo(getActiveNetwork())", + expectedConnectivity, mCm.getNetworkInfo(mCm.getActiveNetwork()).isConnected()); + } + + @Test + public void testVpnUnderlyingNetworkSuspended() throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + + // Connect a VPN. + mMockVpn.establishForMyUid(false, true, false); + callback.expectAvailableCallbacksUnvalidated(mMockVpn); + + // Connect cellular data. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false /* validated */); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Suspend the cellular network and expect the VPN to be suspended. + mCellNetworkAgent.suspend(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + callback.assertNoCallback(); + + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + // VPN's main underlying network is suspended, so no connectivity. + assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + + // Switch to another network. The VPN should no longer be suspended. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false /* validated */); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_WIFI)); + callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Unsuspend cellular and then switch back to it. The VPN remains not suspended. + mCellNetworkAgent.resume(); + mWiFiNetworkAgent.disconnect(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + // Spurious double callback? + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Suspend cellular and expect no connectivity. + mCellNetworkAgent.suspend(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + callback.assertNoCallback(); + + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + + // Resume cellular and expect that connectivity comes back. + mCellNetworkAgent.resume(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + } + + @Test public void testVpnNetworkActive() throws Exception { final int uid = Process.myUid(); @@ -5220,42 +5586,32 @@ public class ConnectivityServiceTest { vpnNetworkCallback.assertNoCallback(); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - final TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); + final Set<UidRange> ranges = uidRangesForUid(uid); + mMockVpn.registerAgent(ranges); + mService.setUnderlyingNetworksForVpn(new Network[0]); + // VPN networks do not satisfy the default request and are automatically validated // by NetworkMonitor assertFalse(NetworkMonitorUtils.isValidationRequired( - vpnNetworkAgent.getNetworkCapabilities())); - vpnNetworkAgent.setNetworkValid(false /* isStrictMode */); + mMockVpn.getAgent().getNetworkCapabilities())); + mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); - vpnNetworkAgent.connect(false); - mMockVpn.connect(); - mMockVpn.setUnderlyingNetworks(new Network[0]); + mMockVpn.connect(false); - genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); + genericNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); - defaultCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); - assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - - genericNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent); - genericNotVpnNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, nc -> null == nc.getUids()); - defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent); + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); ranges.clear(); - vpnNetworkAgent.setUids(ranges); + mMockVpn.setUids(ranges); - genericNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent); + vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); // TODO : The default network callback should actually get a LOST call here (also see the // comment below for AVAILABLE). This is because ConnectivityService does not look at UID @@ -5263,19 +5619,18 @@ public class ConnectivityServiceTest { // can't currently update their UIDs without disconnecting, so this does not matter too // much, but that is the reason the test here has to check for an update to the // capabilities instead of the expected LOST then AVAILABLE. - defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); ranges.add(new UidRange(uid, uid)); mMockVpn.setUids(ranges); - vpnNetworkAgent.setUids(ranges); - genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksValidated(mMockVpn); genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent); + vpnNetworkCallback.expectAvailableCallbacksValidated(mMockVpn); // TODO : Here like above, AVAILABLE would be correct, but because this can't actually // happen outside of the test, ConnectivityService does not rematch callbacks. - defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); mWiFiNetworkAgent.disconnect(); @@ -5285,13 +5640,13 @@ public class ConnectivityServiceTest { vpnNetworkCallback.assertNoCallback(); defaultCallback.assertNoCallback(); - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); - genericNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); genericNotVpnNetworkCallback.assertNoCallback(); wifiNetworkCallback.assertNoCallback(); - vpnNetworkCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent); - defaultCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent); + vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); assertEquals(null, mCm.getActiveNetwork()); mCm.unregisterNetworkCallback(genericNetworkCallback); @@ -5313,20 +5668,13 @@ public class ConnectivityServiceTest { defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */, + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, false /* isStrictMode */); - mMockVpn.connect(); defaultCallback.assertNoCallback(); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); defaultCallback.assertNoCallback(); mCm.unregisterNetworkCallback(defaultCallback); @@ -5345,21 +5693,14 @@ public class ConnectivityServiceTest { defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */, + mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */, false /* isStrictMode */); - mMockVpn.connect(); - defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); - vpnNetworkAgent.disconnect(); - defaultCallback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent); + mMockVpn.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); mCm.unregisterNetworkCallback(defaultCallback); @@ -5377,44 +5718,36 @@ public class ConnectivityServiceTest { callback.assertNoCallback(); // Bring up a VPN that has the INTERNET capability, initially unvalidated. - final int uid = Process.myUid(); - final TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */, + mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */, false /* isStrictMode */); - mMockVpn.connect(); // Even though the VPN is unvalidated, it becomes the default network for our app. - callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mMockVpn); callback.assertNoCallback(); - assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore()); - assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, vpnNetworkAgent.getScore()); - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore()); + assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); - NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork()); + NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED)); assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET)); assertFalse(NetworkMonitorUtils.isValidationRequired( - vpnNetworkAgent.getNetworkCapabilities())); + mMockVpn.getAgent().getNetworkCapabilities())); assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired( - vpnNetworkAgent.getNetworkCapabilities())); + mMockVpn.getAgent().getNetworkCapabilities())); // Pretend that the VPN network validates. - vpnNetworkAgent.setNetworkValid(false /* isStrictMode */); - vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); + mMockVpn.getAgent().mNetworkMonitor.forceReevaluation(Process.myUid()); // Expect to see the validated capability, but no other changes, because the VPN is already // the default network for the app. - callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, vpnNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mMockVpn); callback.assertNoCallback(); - vpnNetworkAgent.disconnect(); - callback.expectCallback(CallbackEntry.LOST, vpnNetworkAgent); + mMockVpn.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mMockVpn); callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent); } @@ -5436,21 +5769,15 @@ public class ConnectivityServiceTest { mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); mCellNetworkAgent.connect(true); - final TestNetworkAgentWrapper vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.connect(); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */, + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, false /* isStrictMode */); - vpnNetworkCallback.expectAvailableCallbacks(vpnNetworkAgent.getNetwork(), + vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(), false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent.getNetwork(), TIMEOUT_MS, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn.getNetwork(), TIMEOUT_MS, nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)); - final NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork()); + final NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertTrue(nc.hasTransport(TRANSPORT_VPN)); assertTrue(nc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(nc.hasTransport(TRANSPORT_WIFI)); @@ -5459,10 +5786,21 @@ public class ConnectivityServiceTest { assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); } + private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) { + final NetworkCapabilities[] defaultCaps = mService.getDefaultNetworkCapabilitiesForUser( + userId, "com.android.calling.package"); + final String defaultCapsString = Arrays.toString(defaultCaps); + assertEquals(defaultCapsString, defaultCaps.length, networks.length); + final Set<NetworkCapabilities> defaultCapsSet = new ArraySet<>(defaultCaps); + for (NetworkAgentWrapper network : networks) { + final NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork()); + final String msg = "Did not find " + nc + " in " + Arrays.toString(defaultCaps); + assertTrue(msg, defaultCapsSet.contains(nc)); + } + } + @Test public void testVpnSetUnderlyingNetworks() throws Exception { - final int uid = Process.myUid(); - final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() .removeCapability(NET_CAPABILITY_NOT_VPN) @@ -5472,18 +5810,11 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); vpnNetworkCallback.assertNoCallback(); - final TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.connect(); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */, + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, false /* isStrictMode */); - vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); - nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork()); + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertTrue(nc.hasTransport(TRANSPORT_VPN)); assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(nc.hasTransport(TRANSPORT_WIFI)); @@ -5492,6 +5823,9 @@ public class ConnectivityServiceTest { // A VPN without underlying networks is not suspended. assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + final int userId = UserHandle.getUserId(Process.myUid()); + assertDefaultNetworkCapabilities(userId /* no networks */); + // Connect cell and use it as an underlying network. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); @@ -5500,11 +5834,12 @@ public class ConnectivityServiceTest { mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent); mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); @@ -5514,59 +5849,65 @@ public class ConnectivityServiceTest { mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Don't disconnect, but note the VPN is not using wifi any more. mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + // The return value of getDefaultNetworkCapabilitiesForUser always includes the default + // network (wifi) as well as the underlying networks (cell). + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Remove NOT_SUSPENDED from the only network and observe VPN is now suspended. mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); - vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, vpnNetworkAgent); + vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); // Add NOT_SUSPENDED again and observe VPN is no longer suspended. mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); - vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, vpnNetworkAgent); + vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); // Use Wifi but not cell. Note the VPN is now unmetered and not suspended. mService.setUnderlyingNetworksForVpn( new Network[] { mWiFiNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) && caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent); // Use both again. mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Cell is suspended again. As WiFi is not, this should not cause a callback. mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); @@ -5575,42 +5916,46 @@ public class ConnectivityServiceTest { // Stop using WiFi. The VPN is suspended again. mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); - // While the SUSPENDED callback should in theory be sent here, it is not. This is - // a bug in ConnectivityService, but as the SUSPENDED and RESUMED callbacks have never - // been public and are deprecated and slated for removal, there is no sense in spending - // resources fixing this bug now. + vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Use both again. mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); - // As above, the RESUMED callback not being sent here is a bug, but not a bug that's - // worth anybody's time to fix. + vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Disconnect cell. Receive update without even removing the dead network from the // underlying networks – it's dead anyway. Not metered any more. mCellNetworkAgent.disconnect(); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) && caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent); // Disconnect wifi too. No underlying networks means this is now metered. mWiFiNetworkAgent.disconnect(); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + // When a network disconnects, the callbacks are fired before all state is updated, so for a + // short time, synchronous calls will behave as if the network is still connected. Wait for + // things to settle. + waitForIdle(); + assertDefaultNetworkCapabilities(userId /* no networks */); mMockVpn.disconnect(); } @@ -5628,18 +5973,11 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); vpnNetworkCallback.assertNoCallback(); - final TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.connect(); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */, + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, false /* isStrictMode */); - vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent); - nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork()); + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertTrue(nc.hasTransport(TRANSPORT_VPN)); assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(nc.hasTransport(TRANSPORT_WIFI)); @@ -5651,7 +5989,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); @@ -5661,7 +5999,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mWiFiNetworkAgent.connect(true); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) && caps.hasCapability(NET_CAPABILITY_NOT_METERED)); @@ -5673,7 +6011,7 @@ public class ConnectivityServiceTest { // Disconnect wifi too. Now we have no default network. mWiFiNetworkAgent.disconnect(); - vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, (caps) -> caps.hasTransport(TRANSPORT_VPN) && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); @@ -5716,18 +6054,10 @@ public class ConnectivityServiceTest { assertTrue(mCm.isActiveNetworkMetered()); // Connect VPN network. By default it is using current default network (Cell). - TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - final int uid = Process.myUid(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true); - mMockVpn.connect(); - waitForIdle(); + mMockVpn.establishForMyUid(); + // Ensure VPN is now the active network. - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); // Expect VPN to be metered. assertTrue(mCm.isActiveNetworkMetered()); @@ -5738,7 +6068,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.connect(true); waitForIdle(); // VPN should still be the active network. - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); // Expect VPN to be unmetered as it should now be using WiFi (new default). assertFalse(mCm.isActiveNetworkMetered()); @@ -5756,7 +6086,6 @@ public class ConnectivityServiceTest { // VPN without any underlying networks is treated as metered. assertTrue(mCm.isActiveNetworkMetered()); - vpnNetworkAgent.disconnect(); mMockVpn.disconnect(); } @@ -5777,18 +6106,10 @@ public class ConnectivityServiceTest { assertFalse(mCm.isActiveNetworkMetered()); // Connect VPN network. - TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - final int uid = Process.myUid(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true); - mMockVpn.connect(); - waitForIdle(); + mMockVpn.establishForMyUid(); + // Ensure VPN is now the active network. - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); // VPN is using Cell mService.setUnderlyingNetworksForVpn( new Network[] { mCellNetworkAgent.getNetwork() }); @@ -5828,7 +6149,6 @@ public class ConnectivityServiceTest { // VPN without underlying networks is treated as metered. assertTrue(mCm.isActiveNetworkMetered()); - vpnNetworkAgent.disconnect(); mMockVpn.disconnect(); } @@ -5843,17 +6163,11 @@ public class ConnectivityServiceTest { assertFalse(mCm.isActiveNetworkMetered()); // Connect VPN network. - TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - final int uid = Process.myUid(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.setUids(ranges); - vpnNetworkAgent.connect(true); - mMockVpn.connectAsAlwaysMetered(); + mMockVpn.registerAgent(true /* isAlwaysMetered */, uidRangesForUid(Process.myUid()), + new LinkProperties()); + mMockVpn.connect(true); waitForIdle(); - assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); // VPN is tracking current platform default (WiFi). mService.setUnderlyingNetworksForVpn(null); @@ -5877,7 +6191,7 @@ public class ConnectivityServiceTest { assertTrue(mCm.isActiveNetworkMetered()); - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); } @Test @@ -5975,6 +6289,19 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(defaultCallback); } + private void checkNetworkInfo(NetworkInfo ni, int type, DetailedState state) { + assertNotNull(ni); + assertEquals(type, ni.getType()); + assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState()); + } + + private void assertActiveNetworkInfo(int type, DetailedState state) { + checkNetworkInfo(mCm.getActiveNetworkInfo(), type, state); + } + private void assertNetworkInfo(int type, DetailedState state) { + checkNetworkInfo(mCm.getNetworkInfo(type), type, state); + } + @Test public final void testLoseTrusted() throws Exception { final NetworkRequest trustedRequest = new NetworkRequest.Builder() @@ -6311,11 +6638,11 @@ public class ConnectivityServiceTest { // prefix discovery is never started. LinkProperties lp = new LinkProperties(baseLp); lp.setNat64Prefix(pref64FromRa); - mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp); - mCellNetworkAgent.connect(false); - final Network network = mCellNetworkAgent.getNetwork(); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp); + mWiFiNetworkAgent.connect(false); + final Network network = mWiFiNetworkAgent.getNetwork(); int netId = network.getNetId(); - callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); @@ -6324,8 +6651,8 @@ public class ConnectivityServiceTest { // If the RA prefix is withdrawn, clatd is stopped and prefix discovery is started. lp.setNat64Prefix(null); - mCellNetworkAgent.sendLinkProperties(lp); - expectNat64PrefixChange(callback, mCellNetworkAgent, null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); inOrder.verify(mMockNetd).clatdStop(iface); inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); @@ -6333,8 +6660,8 @@ public class ConnectivityServiceTest { // If the RA prefix appears while DNS discovery is in progress, discovery is stopped and // clatd is started with the prefix from the RA. lp.setNat64Prefix(pref64FromRa); - mCellNetworkAgent.sendLinkProperties(lp); - expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); @@ -6342,21 +6669,21 @@ public class ConnectivityServiceTest { // Withdraw the RA prefix so we can test the case where an RA prefix appears after DNS // discovery has succeeded. lp.setNat64Prefix(null); - mCellNetworkAgent.sendLinkProperties(lp); - expectNat64PrefixChange(callback, mCellNetworkAgent, null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); inOrder.verify(mMockNetd).clatdStop(iface); inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */, pref64FromDnsStr, 96); - expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromDns); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); // If an RA advertises the same prefix that was discovered by DNS, nothing happens: prefix // discovery is not stopped, and there are no callbacks. lp.setNat64Prefix(pref64FromDns); - mCellNetworkAgent.sendLinkProperties(lp); + mWiFiNetworkAgent.sendLinkProperties(lp); callback.assertNoCallback(); inOrder.verify(mMockNetd, never()).clatdStop(iface); inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); @@ -6366,7 +6693,7 @@ public class ConnectivityServiceTest { // If the RA is later withdrawn, nothing happens again. lp.setNat64Prefix(null); - mCellNetworkAgent.sendLinkProperties(lp); + mWiFiNetworkAgent.sendLinkProperties(lp); callback.assertNoCallback(); inOrder.verify(mMockNetd, never()).clatdStop(iface); inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); @@ -6376,8 +6703,8 @@ public class ConnectivityServiceTest { // If the RA prefix changes, clatd is restarted and prefix discovery is stopped. lp.setNat64Prefix(pref64FromRa); - mCellNetworkAgent.sendLinkProperties(lp); - expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa); inOrder.verify(mMockNetd).clatdStop(iface); inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); @@ -6391,8 +6718,8 @@ public class ConnectivityServiceTest { // If the RA prefix changes, clatd is restarted and prefix discovery is not started. lp.setNat64Prefix(newPref64FromRa); - mCellNetworkAgent.sendLinkProperties(lp); - expectNat64PrefixChange(callback, mCellNetworkAgent, newPref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, newPref64FromRa); inOrder.verify(mMockNetd).clatdStop(iface); inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); inOrder.verify(mMockNetd).clatdStart(iface, newPref64FromRa.toString()); @@ -6402,7 +6729,7 @@ public class ConnectivityServiceTest { // If the RA prefix changes to the same value, nothing happens. lp.setNat64Prefix(newPref64FromRa); - mCellNetworkAgent.sendLinkProperties(lp); + mWiFiNetworkAgent.sendLinkProperties(lp); callback.assertNoCallback(); assertEquals(newPref64FromRa, mCm.getLinkProperties(network).getNat64Prefix()); inOrder.verify(mMockNetd, never()).clatdStop(iface); @@ -6416,19 +6743,19 @@ public class ConnectivityServiceTest { // If the same prefix is learned first by DNS and then by RA, and clat is later stopped, // (e.g., because the network disconnects) setPrefix64(netid, "") is never called. lp.setNat64Prefix(null); - mCellNetworkAgent.sendLinkProperties(lp); - expectNat64PrefixChange(callback, mCellNetworkAgent, null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); inOrder.verify(mMockNetd).clatdStop(iface); inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */, pref64FromDnsStr, 96); - expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromDns); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any()); lp.setNat64Prefix(pref64FromDns); - mCellNetworkAgent.sendLinkProperties(lp); + mWiFiNetworkAgent.sendLinkProperties(lp); callback.assertNoCallback(); inOrder.verify(mMockNetd, never()).clatdStop(iface); inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); @@ -6439,10 +6766,10 @@ public class ConnectivityServiceTest { // When tearing down a network, clat state is only updated after CALLBACK_LOST is fired, but // before CONNECTIVITY_ACTION is sent. Wait for CONNECTIVITY_ACTION before verifying that // clat has been stopped, or the test will be flaky. - ConditionVariable cv = registerConnectivityBroadcast(1); - mCellNetworkAgent.disconnect(); - callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); - waitFor(cv); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + b.expectBroadcast(); inOrder.verify(mMockNetd).clatdStop(iface); inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); @@ -6517,10 +6844,10 @@ public class ConnectivityServiceTest { .destroyNetworkCache(eq(mCellNetworkAgent.getNetwork().netId)); // Disconnect wifi - ConditionVariable cv = registerConnectivityBroadcast(1); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); reset(mNetworkManagementService); mWiFiNetworkAgent.disconnect(); - waitFor(cv); + b.expectBroadcast(); verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME)); // Clean up @@ -6602,34 +6929,21 @@ public class ConnectivityServiceTest { waitForIdle(); assertNull(mService.getProxyForNetwork(null)); - // Set up a VPN network with a proxy - final int uid = Process.myUid(); - final TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN); - final ArraySet<UidRange> ranges = new ArraySet<>(); - ranges.add(new UidRange(uid, uid)); - mMockVpn.setUids(ranges); + // Connect a VPN network with a proxy. LinkProperties testLinkProperties = new LinkProperties(); testLinkProperties.setHttpProxy(testProxyInfo); - vpnNetworkAgent.sendLinkProperties(testLinkProperties); - waitForIdle(); - - // Connect to VPN with proxy - mMockVpn.setNetworkAgent(vpnNetworkAgent); - vpnNetworkAgent.connect(true); - mMockVpn.connect(); - waitForIdle(); + mMockVpn.establishForMyUid(testLinkProperties); // Test that the VPN network returns a proxy, and the WiFi does not. - assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork())); assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); // Test that the VPN network returns no proxy when it is set to null. testLinkProperties.setHttpProxy(null); - vpnNetworkAgent.sendLinkProperties(testLinkProperties); + mMockVpn.sendLinkProperties(testLinkProperties); waitForIdle(); - assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork())); + assertNull(mService.getProxyForNetwork(mMockVpn.getNetwork())); assertNull(mService.getProxyForNetwork(null)); // Set WiFi proxy and check that the vpn proxy is still null. @@ -6640,7 +6954,7 @@ public class ConnectivityServiceTest { // Disconnect from VPN and check that the active network, which is now the WiFi, has the // correct proxy setting. - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); waitForIdle(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); @@ -6655,7 +6969,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange); + mMockVpn.establish(lp, VPN_UID, vpnRange); // Connected VPN should have interface rules set up. There are two expected invocations, // one during VPN uid update, one during VPN LinkProperties update @@ -6665,7 +6979,7 @@ public class ConnectivityServiceTest { assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID); assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange)); - vpnNetworkAgent.disconnect(); + mMockVpn.disconnect(); waitForIdle(); // Disconnected VPN should have interface rules removed @@ -6682,8 +6996,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn( - lp, Process.SYSTEM_UID, vpnRange); + mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); // Legacy VPN should not have interface rules set up verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); @@ -6698,8 +7011,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn( - lp, Process.SYSTEM_UID, vpnRange); + mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); // IPv6 unreachable route should not be misinterpreted as a default route verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); @@ -6713,7 +7025,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange); + mMockVpn.establish(lp, VPN_UID, vpnRange); // Connected VPN should have interface rules set up. There are two expected invocations, // one during VPN uid update, one during VPN LinkProperties update @@ -6725,7 +7037,7 @@ public class ConnectivityServiceTest { reset(mMockNetd); InOrder inOrder = inOrder(mMockNetd); lp.setInterfaceName("tun1"); - vpnNetworkAgent.sendLinkProperties(lp); + mMockVpn.sendLinkProperties(lp); waitForIdle(); // VPN handover (switch to a new interface) should result in rules being updated (old rules // removed first, then new rules added) @@ -6738,7 +7050,7 @@ public class ConnectivityServiceTest { lp = new LinkProperties(); lp.setInterfaceName("tun1"); lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1")); - vpnNetworkAgent.sendLinkProperties(lp); + mMockVpn.sendLinkProperties(lp); waitForIdle(); // VPN not routing everything should no longer have interface filtering rules verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); @@ -6749,7 +7061,7 @@ public class ConnectivityServiceTest { lp.setInterfaceName("tun1"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); - vpnNetworkAgent.sendLinkProperties(lp); + mMockVpn.sendLinkProperties(lp); waitForIdle(); // Back to routing all IPv6 traffic should have filtering rules verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture()); @@ -6764,8 +7076,7 @@ public class ConnectivityServiceTest { lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final UidRange vpnRange = UidRange.createForUser(VPN_USER); - final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, - Collections.singleton(vpnRange)); + mMockVpn.establish(lp, VPN_UID, Collections.singleton(vpnRange)); reset(mMockNetd); InOrder inOrder = inOrder(mMockNetd); @@ -6774,7 +7085,7 @@ public class ConnectivityServiceTest { final Set<UidRange> newRanges = new HashSet<>(Arrays.asList( new UidRange(vpnRange.start, APP1_UID - 1), new UidRange(APP1_UID + 1, vpnRange.stop))); - vpnNetworkAgent.setUids(newRanges); + mMockVpn.setUids(newRanges); waitForIdle(); ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class); @@ -6915,7 +7226,7 @@ public class ConnectivityServiceTest { private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) throws Exception { final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); - establishVpn(new LinkProperties(), vpnOwnerUid, vpnRange); + mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange); mMockVpn.setVpnType(vpnType); final VpnInfo vpnInfo = new VpnInfo(); @@ -6996,19 +7307,6 @@ public class ConnectivityServiceTest { mService.getConnectionOwnerUid(getTestConnectionInfo()); } - private TestNetworkAgentWrapper establishVpn( - LinkProperties lp, int ownerUid, Set<UidRange> vpnRange) throws Exception { - final TestNetworkAgentWrapper - vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp); - vpnNetworkAgent.getNetworkCapabilities().setOwnerUid(ownerUid); - mMockVpn.setNetworkAgent(vpnNetworkAgent); - mMockVpn.connect(); - mMockVpn.setUids(vpnRange); - vpnNetworkAgent.connect(true); - waitForIdle(); - return vpnNetworkAgent; - } - private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) { final PackageInfo packageInfo = new PackageInfo(); if (hasSystemPermission) { @@ -7188,22 +7486,28 @@ public class ConnectivityServiceTest { setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION); - mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); // setUp() calls mockVpn() which adds a VPN with the Test Runner's uid. Configure it to be // active final VpnInfo info = new VpnInfo(); info.ownerUid = Process.myUid(); - info.vpnIface = "interface"; + info.vpnIface = VPN_IFNAME; mMockVpn.setVpnInfo(info); - mMockVpn.overrideUnderlyingNetworks(new Network[] {network}); + + mMockVpn.establishForMyUid(); + waitForIdle(); + + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + + assertTrue(mService.setUnderlyingNetworksForVpn(new Network[] {network})); assertTrue( "Active VPN permission not applied", mService.checkConnectivityDiagnosticsPermissions( Process.myPid(), Process.myUid(), naiWithoutUid, mContext.getOpPackageName())); - mMockVpn.overrideUnderlyingNetworks(null); + assertTrue(mService.setUnderlyingNetworksForVpn(null)); assertFalse( "VPN shouldn't receive callback on non-underlying network", mService.checkConnectivityDiagnosticsPermissions( @@ -7224,8 +7528,6 @@ public class ConnectivityServiceTest { Manifest.permission.ACCESS_FINE_LOCATION); mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); - // Disconnect mock vpn so the uid check on NetworkAgentInfo is tested - mMockVpn.disconnect(); assertTrue( "NetworkCapabilities administrator uid permission not applied", mService.checkConnectivityDiagnosticsPermissions( |