1 /*
2  * Copyright (C) 2024 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.appsearch;
18 
19 import static android.Manifest.permission.RECEIVE_BOOT_COMPLETED;
20 
21 import static com.android.server.appsearch.AppSearchMaintenanceService.MIN_APPSEARCH_MAINTENANCE_JOB_ID;
22 
23 import static com.google.common.truth.Truth.assertThat;
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.Mockito.doNothing;
29 import static org.mockito.Mockito.doThrow;
30 import static org.mockito.Mockito.spy;
31 import static org.mockito.Mockito.verify;
32 
33 import android.annotation.Nullable;
34 import android.app.UiAutomation;
35 import android.app.job.JobInfo;
36 import android.app.job.JobScheduler;
37 import android.content.Context;
38 import android.content.ContextWrapper;
39 import android.os.CancellationSignal;
40 
41 import androidx.test.core.app.ApplicationProvider;
42 import androidx.test.platform.app.InstrumentationRegistry;
43 
44 import com.android.dx.mockito.inline.extended.ExtendedMockito;
45 import com.android.server.LocalManagerRegistry;
46 
47 import org.junit.After;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.mockito.Mock;
51 import org.mockito.Mockito;
52 import org.mockito.MockitoAnnotations;
53 import org.mockito.MockitoSession;
54 
55 public class AppSearchMaintenanceServiceTest {
56     private static final int DEFAULT_USER_ID = 0;
57 
58     private Context mContext = ApplicationProvider.getApplicationContext();
59     private Context mContextWrapper;
60     private AppSearchMaintenanceService mAppSearchMaintenanceService;
61     private MockitoSession session;
62     @Mock
63     private JobScheduler mockJobScheduler;
64 
65     @Before
setUp()66     public void setUp() {
67         MockitoAnnotations.initMocks(this);
68         mContextWrapper = new ContextWrapper(mContext) {
69             @Override
70             @Nullable
71             public Object getSystemService(String name) {
72                 if (Context.JOB_SCHEDULER_SERVICE.equals(name)) {
73                     return mockJobScheduler;
74                 }
75                 return getSystemService(name);
76             }
77         };
78         mAppSearchMaintenanceService = spy(new AppSearchMaintenanceService());
79         doNothing().when(mAppSearchMaintenanceService).jobFinished(any(), anyBoolean());
80         session = ExtendedMockito.mockitoSession().
81                 mockStatic(LocalManagerRegistry.class).
82                 startMocking();
83     }
84 
85     @After
tearDown()86     public void tearDown() {
87         session.finishMocking();
88     }
89 
90     @Test
testScheduleFullPersistJob()91     public void testScheduleFullPersistJob() {
92         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
93         long intervalMillis = 37 * 60 * 1000; // 37 Min
94         try {
95             uiAutomation.adoptShellPermissionIdentity(RECEIVE_BOOT_COMPLETED);
96             AppSearchMaintenanceService.scheduleFullyPersistJob(mContext, /*userId=*/123,
97                     intervalMillis);
98         } finally {
99             uiAutomation.dropShellPermissionIdentity();
100         }
101 
102         int jobId = MIN_APPSEARCH_MAINTENANCE_JOB_ID + 123;
103         JobInfo jobInfo = mContext.getSystemService(JobScheduler.class).getPendingJob(jobId);
104         assertThat(jobInfo).isNotNull();
105         assertThat(jobInfo.isRequireBatteryNotLow()).isTrue();
106         assertThat(jobInfo.isRequireDeviceIdle()).isTrue();
107         assertThat(jobInfo.isPersisted()).isTrue();
108         assertThat(jobInfo.isPeriodic()).isTrue();
109         assertThat(jobInfo.getIntervalMillis()).isEqualTo(intervalMillis);
110 
111         int userId = jobInfo.getExtras().getInt("user_id", /*defaultValue=*/ -1);
112         assertThat(userId).isEqualTo(123);
113     }
114 
115     @Test
testDoFullPersistForUser_withInitializedLocalService_isSuccessful()116     public void testDoFullPersistForUser_withInitializedLocalService_isSuccessful() {
117         ExtendedMockito.doReturn(Mockito.mock(AppSearchManagerService.LocalService.class))
118                 .when(() -> LocalManagerRegistry.getManager(
119                         AppSearchManagerService.LocalService.class));
120         assertThat(mAppSearchMaintenanceService
121                 .doFullyPersistJobForUser(mContextWrapper, null, 0, new CancellationSignal()))
122                 .isTrue();
123     }
124 
125     @Test
testDoFullPersistForUser_withUninitializedLocalService_failsGracefully()126     public void testDoFullPersistForUser_withUninitializedLocalService_failsGracefully() {
127         ExtendedMockito.doReturn(null)
128                 .when(() -> LocalManagerRegistry.getManager(
129                         AppSearchManagerService.LocalService.class));
130         assertThat(mAppSearchMaintenanceService
131                 .doFullyPersistJobForUser(mContextWrapper, null, 0, new CancellationSignal()))
132                 .isFalse();
133     }
134 
135     @Test
testDoFullPersistForUser_onEncounteringException_failsGracefully()136     public void testDoFullPersistForUser_onEncounteringException_failsGracefully()
137             throws Exception {
138         AppSearchManagerService.LocalService mockService = Mockito.mock(
139                 AppSearchManagerService.LocalService.class);
140         doThrow(RuntimeException.class).when(mockService).doFullyPersistForUser(anyInt());
141         ExtendedMockito.doReturn(mockService)
142                 .when(() -> LocalManagerRegistry.getManager(
143                         AppSearchManagerService.LocalService.class));
144 
145         assertThat(mAppSearchMaintenanceService
146                 .doFullyPersistJobForUser(mContextWrapper, null, 0, new CancellationSignal()))
147                 .isFalse();
148     }
149 
150     @Test
testDoFullPersistForUser_checkPendingJobIfNotInitialized()151     public void testDoFullPersistForUser_checkPendingJobIfNotInitialized() {
152         ExtendedMockito.doReturn(null)
153                 .when(() -> LocalManagerRegistry.getManager(
154                         AppSearchManagerService.LocalService.class));
155 
156         mAppSearchMaintenanceService.doFullyPersistJobForUser(
157                 mContextWrapper, /*params=*/null, /*userId=*/123, new CancellationSignal());
158 
159         // The server is not initialized, we should check and cancel any pending job. There will
160       // be a
161         // getPendingJob call to the job scheduler only. Since we haven't schedule any job.
162         verify(mockJobScheduler).getPendingJob(MIN_APPSEARCH_MAINTENANCE_JOB_ID + 123);
163     }
164 
165     @Test
testCancelPendingFullPersistJob_succeeds()166     public void testCancelPendingFullPersistJob_succeeds() {
167         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
168         try {
169             uiAutomation.adoptShellPermissionIdentity(RECEIVE_BOOT_COMPLETED);
170             AppSearchMaintenanceService.scheduleFullyPersistJob(mContext, DEFAULT_USER_ID,
171                     /*intervalMillis=*/456L);
172         } finally {
173             uiAutomation.dropShellPermissionIdentity();
174         }
175         int jobId = MIN_APPSEARCH_MAINTENANCE_JOB_ID + DEFAULT_USER_ID;
176         JobInfo jobInfo = mContext.getSystemService(JobScheduler.class).getPendingJob(jobId);
177         assertThat(jobInfo).isNotNull();
178 
179         AppSearchMaintenanceService.cancelFullyPersistJobIfScheduled(mContext, DEFAULT_USER_ID);
180 
181         jobInfo = mContext.getSystemService(JobScheduler.class).getPendingJob(jobId);
182         assertThat(jobInfo).isNull();
183     }
184 
185     @Test
test_onStartJob_handlesExceptionGracefully()186     public void test_onStartJob_handlesExceptionGracefully() {
187         mAppSearchMaintenanceService.onStartJob(null);
188     }
189 
190     @Test
test_onStopJob_handlesExceptionGracefully()191     public void test_onStopJob_handlesExceptionGracefully() {
192         mAppSearchMaintenanceService.onStopJob(null);
193     }
194 }
195