1 /*
2  * Copyright (C) 2022 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.grammaticalinflection;
18 
19 import static junit.framework.Assert.assertNull;
20 import static junit.framework.Assert.assertTrue;
21 import static junit.framework.Assert.assertEquals;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.anyInt;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.verify;
28 
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.content.res.Configuration;
32 
33 import androidx.test.ext.junit.runners.AndroidJUnit4;
34 
35 import com.google.common.collect.Maps;
36 
37 import org.junit.Before;
38 import org.junit.Rule;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 import org.mockito.Mock;
42 import org.mockito.junit.MockitoJUnit;
43 import org.mockito.junit.MockitoRule;
44 
45 import java.io.ByteArrayInputStream;
46 import java.io.ByteArrayOutputStream;
47 import java.io.IOException;
48 import java.io.ObjectInputStream;
49 import java.io.ObjectOutputStream;
50 import java.nio.ByteBuffer;
51 import java.util.HashMap;
52 import java.util.List;
53 
54 @RunWith(AndroidJUnit4.class)
55 public class GrammaticalInflectionBackupTest {
56     private static final int DEFAULT_USER_ID = 0;
57     private static final String DEFAULT_PACKAGE_NAME = "com.test.package.name";
58 
59     @Rule
60     public final MockitoRule mockito = MockitoJUnit.rule();
61 
62     @Mock
63     private PackageManager mMockPackageManager;
64     @Mock
65     private GrammaticalInflectionService mGrammaticalInflectionService;
66 
67     private GrammaticalInflectionBackupHelper mBackupHelper;
68 
69     @Before
setUp()70     public void setUp() throws Exception {
71         mBackupHelper = new GrammaticalInflectionBackupHelper(
72                 null, mGrammaticalInflectionService, mMockPackageManager);
73     }
74 
75     @Test
testBackupPayload_noAppsInstalled_returnsNull()76     public void testBackupPayload_noAppsInstalled_returnsNull() {
77         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
78     }
79 
80     @Test
testBackupPayload_AppsInstalled_returnsGender()81     public void testBackupPayload_AppsInstalled_returnsGender()
82             throws IOException, ClassNotFoundException {
83         mockAppInstalled();
84         mockGetApplicationGrammaticalGender(Configuration.GRAMMATICAL_GENDER_MASCULINE);
85 
86         HashMap<String, Integer> payload =
87                 readFromByteArray(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
88 
89         // verify the payload
90         HashMap<String, Integer> expectationMap = new HashMap<>();
91         expectationMap.put(DEFAULT_PACKAGE_NAME, Configuration.GRAMMATICAL_GENDER_MASCULINE);
92         assertTrue(Maps.difference(payload, expectationMap).areEqual());
93     }
94 
95     @Test
testApplyPayload_onPackageAdded_setApplicationGrammaticalGender()96     public void testApplyPayload_onPackageAdded_setApplicationGrammaticalGender()
97             throws IOException {
98         mockAppInstalled();
99 
100         HashMap<String, Integer> testData = new HashMap<>();
101         testData.put(DEFAULT_PACKAGE_NAME, Configuration.GRAMMATICAL_GENDER_NEUTRAL);
102         mBackupHelper.stageAndApplyRestoredPayload(convertToByteArray(testData), DEFAULT_USER_ID);
103         mBackupHelper.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
104 
105         verify(mGrammaticalInflectionService).setRequestedApplicationGrammaticalGender(
106                 eq(DEFAULT_PACKAGE_NAME),
107                 eq(DEFAULT_USER_ID),
108                 eq(Configuration.GRAMMATICAL_GENDER_NEUTRAL));
109     }
110 
111     @Test
testSystemBackupPayload_returnsGender()112     public void testSystemBackupPayload_returnsGender()
113             throws IOException, ClassNotFoundException {
114         doReturn(Configuration.GRAMMATICAL_GENDER_MASCULINE).when(mGrammaticalInflectionService)
115                 .getSystemGrammaticalGender(eq(DEFAULT_USER_ID));
116 
117         int gender = convertByteArrayToInt(mBackupHelper.getSystemBackupPayload(DEFAULT_USER_ID));
118 
119         assertEquals(gender, Configuration.GRAMMATICAL_GENDER_MASCULINE);
120     }
121 
122     @Test
testApplySystemPayload_setSystemWideGrammaticalGender()123     public void testApplySystemPayload_setSystemWideGrammaticalGender()
124             throws IOException {
125         mBackupHelper.applyRestoredSystemPayload(
126                 intToByteArray(Configuration.GRAMMATICAL_GENDER_NEUTRAL), DEFAULT_USER_ID);
127 
128         verify(mGrammaticalInflectionService).setSystemWideGrammaticalGender(
129                 eq(Configuration.GRAMMATICAL_GENDER_NEUTRAL),
130                 eq(DEFAULT_USER_ID));
131     }
132 
mockAppInstalled()133     private void mockAppInstalled() {
134         ApplicationInfo dummyApp = new ApplicationInfo();
135         dummyApp.packageName = DEFAULT_PACKAGE_NAME;
136         doReturn(List.of(dummyApp)).when(mMockPackageManager)
137                 .getInstalledApplicationsAsUser(any(), anyInt());
138     }
139 
mockGetApplicationGrammaticalGender(int grammaticalGender)140     private void mockGetApplicationGrammaticalGender(int grammaticalGender) {
141         doReturn(grammaticalGender).when(mGrammaticalInflectionService)
142                 .getApplicationGrammaticalGender(
143                         eq(DEFAULT_PACKAGE_NAME), eq(DEFAULT_USER_ID));
144     }
145 
convertToByteArray(HashMap<String, Integer> pkgGenderInfo)146     private byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) throws IOException{
147         try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
148              final ObjectOutputStream objStream = new ObjectOutputStream(out)) {
149             objStream.writeObject(pkgGenderInfo);
150             return out.toByteArray();
151         } catch (IOException e) {
152             throw e;
153         }
154     }
155 
readFromByteArray(byte[] payload)156     private HashMap<String, Integer> readFromByteArray(byte[] payload)
157             throws IOException, ClassNotFoundException {
158         HashMap<String, Integer> data;
159 
160         try (ByteArrayInputStream byteIn = new ByteArrayInputStream(payload);
161              ObjectInputStream in = new ObjectInputStream(byteIn)) {
162             data = (HashMap<String, Integer>) in.readObject();
163         } catch (IOException | ClassNotFoundException e) {
164             throw e;
165         }
166         return data;
167     }
168 
intToByteArray(final int gender)169     private byte[] intToByteArray(final int gender) {
170         ByteBuffer bb = ByteBuffer.allocate(4);
171         bb.putInt(gender);
172         return bb.array();
173     }
174 
convertByteArrayToInt(byte[] intBytes)175     private int convertByteArrayToInt(byte[] intBytes) {
176         ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
177         return byteBuffer.getInt();
178     }
179 }
180