1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
22 
23 import static com.google.common.truth.Truth.assertWithMessage;
24 
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.ArgumentMatchers.anyBoolean;
27 import static org.mockito.ArgumentMatchers.anyString;
28 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.ArgumentMatchers.same;
30 import static org.mockito.Mockito.never;
31 import static org.mockito.Mockito.when;
32 
33 import android.app.Activity;
34 import android.content.BroadcastReceiver;
35 import android.content.Context;
36 import android.content.ContextWrapper;
37 import android.content.Intent;
38 import android.os.Looper;
39 import android.os.RecoverySystem;
40 import android.os.UserHandle;
41 import android.os.UserManager;
42 import android.os.storage.StorageManager;
43 import android.platform.test.annotations.Presubmit;
44 import android.util.Log;
45 
46 import androidx.test.InstrumentationRegistry;
47 
48 import org.junit.After;
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.mockito.Mock;
52 import org.mockito.MockitoSession;
53 import org.mockito.quality.Strictness;
54 
55 import java.util.concurrent.CountDownLatch;
56 import java.util.concurrent.TimeUnit;
57 
58 /**
59  * Run it as {@code FrameworksMockingServicesTests:MasterClearReceiverTest}.
60  */
61 @Presubmit
62 public final class MasterClearReceiverTest {
63 
64     private static final String TAG = MasterClearReceiverTest.class.getSimpleName();
65 
66     private MockitoSession mSession;
67 
68     // Cannot @Mock context because MasterClearReceiver shows an AlertDialog, which relies
69     // on resources - we'd need to mock them as well.
70     private final Context mContext = new ContextWrapper(
71             InstrumentationRegistry.getInstrumentation().getTargetContext()) {
72 
73         @Override
74         public Object getSystemService(String name) {
75             Log.v(TAG, "getSystemService(): " + name);
76             if (name.equals(Context.STORAGE_SERVICE)) {
77                 return mSm;
78             }
79             if (name.equals(Context.USER_SERVICE)) {
80                 return mUserManager;
81             }
82             return super.getSystemService(name);
83         }
84     };
85 
86     private final MasterClearReceiver mReceiver = new MasterClearReceiver();
87 
88     // Used to make sure that wipeAdoptableDisks() is called before rebootWipeUserData()
89     private boolean mWipeExternalDataCalled;
90 
91     // Uset to block test until rebootWipeUserData() is called, as it might be asynchronous called
92     // in a different thread
93     private final CountDownLatch mRebootWipeUserDataLatch = new CountDownLatch(1);
94 
95     @Mock
96     private StorageManager mSm;
97 
98     @Mock
99     private UserManager mUserManager;
100 
101     @Before
startSession()102     public void startSession() {
103         mSession = mockitoSession()
104                 .initMocks(this)
105                 .mockStatic(RecoverySystem.class)
106                 .mockStatic(UserManager.class)
107                 .strictness(Strictness.LENIENT)
108                 .startMocking();
109         setPendingResultForUser(UserHandle.myUserId());
110     }
111 
112     @After
finishSession()113     public void finishSession() {
114         if (mSession == null) {
115             Log.w(TAG, "finishSession(): no session");
116             return;
117         }
118         mSession.finishMocking();
119     }
120 
121     @Test
testNoExtras()122     public void testNoExtras() throws Exception {
123         expectNoWipeExternalData();
124         expectRebootWipeUserData();
125 
126         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
127         mReceiver.onReceive(mContext, intent);
128 
129         verifyRebootWipeUserData();
130         verifyNoWipeExternalData();
131     }
132 
133     @Test
testWipeExternalDirectory()134     public void testWipeExternalDirectory() throws Exception {
135         expectWipeExternalData();
136         expectRebootWipeUserData();
137 
138         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
139         intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
140         mReceiver.onReceive(mContext, intent);
141 
142         verifyRebootWipeUserData();
143         verifyWipeExternalData();
144     }
145 
146     @Test
testAllExtras()147     public void testAllExtras() throws Exception {
148         expectWipeExternalData();
149         expectRebootWipeUserData();
150 
151         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
152         intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
153         intent.putExtra("shutdown", true);
154         intent.putExtra(Intent.EXTRA_REASON, "Self destruct");
155         intent.putExtra(Intent.EXTRA_FORCE_FACTORY_RESET, true);
156         intent.putExtra(Intent.EXTRA_WIPE_ESIMS, true);
157         intent.putExtra("keep_memtag_mode", true);
158         mReceiver.onReceive(mContext, intent);
159 
160         verifyRebootWipeUserData(/* shutdown= */ true, /* reason= */ "Self destruct",
161                 /* force= */ true, /* wipeEuicc= */ true, /* keepMemtagMode= */ true);
162         verifyWipeExternalData();
163     }
164 
165     @Test
testNonSystemUser()166     public void testNonSystemUser() throws Exception {
167         expectWipeNonSystemUser();
168 
169         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
170         setPendingResultForUser(/* userId= */ 10);
171         mReceiver.onReceive(mContext, intent);
172 
173         verifyNoRebootWipeUserData();
174         verifyNoWipeExternalData();
175         verifyWipeNonSystemUser();
176     }
177 
178     @Test
testHeadlessSystemUser()179     public void testHeadlessSystemUser() throws Exception {
180         expectNoWipeExternalData();
181         expectRebootWipeUserData();
182         expectHeadlessSystemUserMode();
183 
184         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
185         setPendingResultForUser(/* userId= */ 10);
186         mReceiver.onReceive(mContext, intent);
187 
188         verifyRebootWipeUserData();
189         verifyNoWipeExternalData();
190     }
191 
expectNoWipeExternalData()192     private void expectNoWipeExternalData() {
193         // This is a trick to simplify how the order of methods are called: as wipeAdoptableDisks()
194         // should be called before rebootWipeUserData(), expectRebootWipeUserData() throws an
195         // exception if it's not called, so this method "emulates" a call when it's not neeeded.
196         //
197         // A more robust solution would be using internal counters for expected and actual mocked
198         // calls, so the expectXXX() methods would increment expected counter and the Answer
199         // implementations would increment the actual counter and check if they match, but that
200         // would be an overkill (and make the test logic more complicated).
201         mWipeExternalDataCalled = true;
202     }
203 
expectRebootWipeUserData()204     private void expectRebootWipeUserData() {
205         doAnswer((inv) -> {
206             Log.i(TAG, inv.toString());
207             if (!mWipeExternalDataCalled) {
208                 String error = "rebootWipeUserData() called before wipeAdoptableDisks()";
209                 Log.e(TAG, error);
210                 throw new IllegalStateException(error);
211             }
212             mRebootWipeUserDataLatch.countDown();
213             return null;
214         }).when(() -> RecoverySystem
215                 .rebootWipeUserData(any(), anyBoolean(), any(), anyBoolean(), anyBoolean(), anyBoolean()));
216     }
217 
expectWipeExternalData()218     private void expectWipeExternalData() {
219         Looper.prepare(); // needed by Dialog
220 
221         doAnswer((inv) -> {
222             Log.i(TAG, inv.toString());
223             mWipeExternalDataCalled = true;
224             return null;
225         }).when(mSm).wipeAdoptableDisks();
226     }
227 
expectWipeNonSystemUser()228     private void expectWipeNonSystemUser() {
229         when(mUserManager.removeUserWhenPossible(any(), anyBoolean()))
230                 .thenReturn(UserManager.REMOVE_RESULT_REMOVED);
231     }
232 
expectHeadlessSystemUserMode()233     private void expectHeadlessSystemUserMode() {
234         doAnswer((inv) -> {
235             Log.i(TAG, inv.toString());
236             return true;
237         }).when(() -> UserManager.isHeadlessSystemUserMode());
238     }
239 
verifyRebootWipeUserData()240     private void verifyRebootWipeUserData() throws Exception  {
241         verifyRebootWipeUserData(/* shutdown= */ false, /* reason= */ null, /* force= */ false,
242                 /* wipeEuicc= */ false);
243 
244     }
245 
verifyRebootWipeUserData(boolean shutdown, String reason, boolean force, boolean wipeEuicc)246     private void verifyRebootWipeUserData(boolean shutdown, String reason, boolean force,
247             boolean wipeEuicc) throws Exception {
248         verifyRebootWipeUserData(shutdown, reason, force, wipeEuicc, /* keepMemtagMode= */ false);
249     }
250 
verifyRebootWipeUserData(boolean shutdown, String reason, boolean force, boolean wipeEuicc, boolean keepMemtagMode)251     private void verifyRebootWipeUserData(boolean shutdown, String reason, boolean force,
252             boolean wipeEuicc, boolean keepMemtagMode) throws Exception {
253         boolean called = mRebootWipeUserDataLatch.await(5, TimeUnit.SECONDS);
254         assertWithMessage("rebootWipeUserData not called in 5s").that(called).isTrue();
255 
256         verify(()-> RecoverySystem.rebootWipeUserData(same(mContext), eq(shutdown), eq(reason),
257                 eq(force), eq(wipeEuicc), eq(keepMemtagMode)));
258     }
259 
verifyNoRebootWipeUserData()260     private void verifyNoRebootWipeUserData() {
261         verify(()-> RecoverySystem.rebootWipeUserData(
262                 any(), anyBoolean(), anyString(), anyBoolean(), anyBoolean()), never());
263     }
264 
verifyWipeExternalData()265     private void verifyWipeExternalData() {
266         verify(mSm).wipeAdoptableDisks();
267     }
268 
verifyNoWipeExternalData()269     private void verifyNoWipeExternalData() {
270         verify(mSm, never()).wipeAdoptableDisks();
271     }
272 
verifyWipeNonSystemUser()273     private void verifyWipeNonSystemUser() {
274         verify(mUserManager).removeUserWhenPossible(any(), anyBoolean());
275     }
276 
setPendingResultForUser(int userId)277     private void setPendingResultForUser(int userId) {
278         mReceiver.setPendingResult(new BroadcastReceiver.PendingResult(
279                 Activity.RESULT_OK,
280                 "resultData",
281                 /* resultExtras= */ null,
282                 BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
283                 /* ordered= */ true,
284                 /* sticky= */ false,
285                 /* token= */ null,
286                 userId,
287                 /* flags= */ 0));
288     }
289 }
290