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.anyInt;
28 import static org.mockito.ArgumentMatchers.anyString;
29 import static org.mockito.ArgumentMatchers.eq;
30 import static org.mockito.ArgumentMatchers.same;
31 import static org.mockito.Mockito.never;
32 import static org.mockito.Mockito.when;
33 
34 import android.app.Activity;
35 import android.content.BroadcastReceiver;
36 import android.content.Context;
37 import android.content.ContextWrapper;
38 import android.content.Intent;
39 import android.os.Looper;
40 import android.os.RecoverySystem;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.os.storage.StorageManager;
44 import android.platform.test.annotations.Presubmit;
45 import android.util.Log;
46 
47 import androidx.test.InstrumentationRegistry;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.mockito.Mock;
53 import org.mockito.MockitoSession;
54 import org.mockito.quality.Strictness;
55 
56 import java.util.concurrent.CountDownLatch;
57 import java.util.concurrent.TimeUnit;
58 
59 /**
60  * Run it as {@code FrameworksMockingServicesTests:MasterClearReceiverTest}.
61  */
62 @Presubmit
63 public final class MasterClearReceiverTest {
64 
65     private static final String TAG = MasterClearReceiverTest.class.getSimpleName();
66 
67     private MockitoSession mSession;
68 
69     // Cannot @Mock context because MasterClearReceiver shows an AlertDialog, which relies
70     // on resources - we'd need to mock them as well.
71     private final Context mContext = new ContextWrapper(
72             InstrumentationRegistry.getInstrumentation().getTargetContext()) {
73 
74         @Override
75         public Object getSystemService(String name) {
76             Log.v(TAG, "getSystemService(): " + name);
77             if (name.equals(Context.STORAGE_SERVICE)) {
78                 return mSm;
79             }
80             if (name.equals(Context.USER_SERVICE)) {
81                 return mUserManager;
82             }
83             return super.getSystemService(name);
84         }
85     };
86 
87     private final MasterClearReceiver mReceiver = new MasterClearReceiver();
88 
89     // Used to make sure that wipeAdoptableDisks() is called before rebootWipeUserData()
90     private boolean mWipeExternalDataCalled;
91 
92     // Uset to block test until rebootWipeUserData() is called, as it might be asynchronous called
93     // in a different thread
94     private final CountDownLatch mRebootWipeUserDataLatch = new CountDownLatch(1);
95 
96     @Mock
97     private StorageManager mSm;
98 
99     @Mock
100     private UserManager mUserManager;
101 
102     @Before
startSession()103     public void startSession() {
104         mSession = mockitoSession()
105                 .initMocks(this)
106                 .mockStatic(RecoverySystem.class)
107                 .mockStatic(UserManager.class)
108                 .strictness(Strictness.LENIENT)
109                 .startMocking();
110         setPendingResultForUser(UserHandle.myUserId());
111     }
112 
113     @After
finishSession()114     public void finishSession() {
115         if (mSession == null) {
116             Log.w(TAG, "finishSession(): no session");
117             return;
118         }
119         mSession.finishMocking();
120     }
121 
122     @Test
testNoExtras()123     public void testNoExtras() throws Exception {
124         expectNoWipeExternalData();
125         expectRebootWipeUserData();
126 
127         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
128         mReceiver.onReceive(mContext, intent);
129 
130         verifyRebootWipeUserData();
131         verifyNoWipeExternalData();
132     }
133 
134     @Test
testWipeExternalDirectory()135     public void testWipeExternalDirectory() throws Exception {
136         expectWipeExternalData();
137         expectRebootWipeUserData();
138 
139         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
140         intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
141         mReceiver.onReceive(mContext, intent);
142 
143         verifyRebootWipeUserData();
144         verifyWipeExternalData();
145     }
146 
147     @Test
testAllExtras()148     public void testAllExtras() throws Exception {
149         expectWipeExternalData();
150         expectRebootWipeUserData();
151 
152         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
153         intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
154         intent.putExtra("shutdown", true);
155         intent.putExtra(Intent.EXTRA_REASON, "Self destruct");
156         intent.putExtra(Intent.EXTRA_FORCE_FACTORY_RESET, true);
157         intent.putExtra(Intent.EXTRA_WIPE_ESIMS, true);
158         mReceiver.onReceive(mContext, intent);
159 
160         verifyRebootWipeUserData(/* shutdown= */ true, /* reason= */ "Self destruct",
161                 /* force= */ true, /* wipeEuicc= */ 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()));
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.removeUserOrSetEphemeral(anyInt(), 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         boolean called = mRebootWipeUserDataLatch.await(5, TimeUnit.SECONDS);
249         assertWithMessage("rebootWipeUserData not called in 5s").that(called).isTrue();
250 
251         verify(()-> RecoverySystem.rebootWipeUserData(same(mContext), eq(shutdown), eq(reason),
252                 eq(force), eq(wipeEuicc)));
253     }
254 
verifyNoRebootWipeUserData()255     private void verifyNoRebootWipeUserData() {
256         verify(()-> RecoverySystem.rebootWipeUserData(
257                 any(), anyBoolean(), anyString(), anyBoolean(), anyBoolean()), never());
258     }
259 
verifyWipeExternalData()260     private void verifyWipeExternalData() {
261         verify(mSm).wipeAdoptableDisks();
262     }
263 
verifyNoWipeExternalData()264     private void verifyNoWipeExternalData() {
265         verify(mSm, never()).wipeAdoptableDisks();
266     }
267 
verifyWipeNonSystemUser()268     private void verifyWipeNonSystemUser() {
269         verify(mUserManager).removeUserOrSetEphemeral(anyInt(), anyBoolean());
270     }
271 
setPendingResultForUser(int userId)272     private void setPendingResultForUser(int userId) {
273         mReceiver.setPendingResult(new BroadcastReceiver.PendingResult(
274                 Activity.RESULT_OK,
275                 "resultData",
276                 /* resultExtras= */ null,
277                 BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
278                 /* ordered= */ true,
279                 /* sticky= */ false,
280                 /* token= */ null,
281                 userId,
282                 /* flags= */ 0));
283     }
284 }
285