1 /*
2  * Copyright (C) 2017 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 android.content.pm.cts;
18 
19 import static android.content.pm.PackageInfo.INSTALL_LOCATION_AUTO;
20 import static android.content.pm.PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
21 import static android.content.pm.PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
22 import static android.content.pm.PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
23 import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
24 import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
25 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
26 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
27 import static android.content.pm.PackageManager.INSTALL_REASON_POLICY;
28 import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
29 import static android.content.pm.PackageManager.INSTALL_REASON_USER;
30 
31 import static com.google.common.truth.Truth.assertThat;
32 
33 import static org.junit.Assert.fail;
34 
35 import android.content.Context;
36 import android.content.pm.PackageInstaller;
37 import android.content.pm.PackageInstaller.SessionInfo;
38 import android.content.pm.PackageInstaller.SessionParams;
39 import android.graphics.Bitmap;
40 import android.net.Uri;
41 import android.platform.test.annotations.AppModeFull;
42 import android.util.Log;
43 
44 import androidx.test.InstrumentationRegistry;
45 
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.junit.runners.Parameterized;
49 
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.List;
53 import java.util.function.Consumer;
54 
55 @RunWith(Parameterized.class)
56 @AppModeFull // TODO(Instant) Figure out which APIs should work.
57 public class InstallSessionParamsUnitTest {
58     private static final String LOG_TAG = InstallSessionParamsUnitTest.class.getSimpleName();
59     private static Optional UNSET = new Optional(false, null);
60 
61     @Parameterized.Parameter(0)
62     public Optional<Integer> mode;
63     @Parameterized.Parameter(1)
64     public Optional<Integer> installLocation;
65     @Parameterized.Parameter(2)
66     public Optional<Integer> size;
67     @Parameterized.Parameter(3)
68     public Optional<String> appPackageName;
69     @Parameterized.Parameter(4)
70     public Optional<Bitmap> appIcon;
71     @Parameterized.Parameter(5)
72     public Optional<String> appLabel;
73     @Parameterized.Parameter(6)
74     public Optional<Uri> originatingUri;
75     @Parameterized.Parameter(7)
76     public Optional<Integer> originatingUid;
77     @Parameterized.Parameter(8)
78     public Optional<Uri> referredUri;
79     @Parameterized.Parameter(9)
80     public Optional<Integer> installReason;
81     @Parameterized.Parameter(10)
82     public boolean expectFailure;
83 
84     /**
85      * Generate test-parameters where all params are the same, but one param cycles through all
86      * values.
87      */
getSingleParameterChangingTests( Object[][][] allParameterValues, int changingParameterIndex, Object[] changingParameterValues, boolean expectFailure)88     private static ArrayList<Object[]> getSingleParameterChangingTests(
89             Object[][][] allParameterValues, int changingParameterIndex,
90             Object[] changingParameterValues, boolean expectFailure) {
91         ArrayList<Object[]> params = new ArrayList<>();
92 
93         for (Object changingParameterValue : changingParameterValues) {
94             ArrayList<Object> singleTestParams = new ArrayList<>();
95 
96             // parameterIndex is the index of the parameter (0 = mode, ...)
97             for (int parameterIndex = 0; parameterIndex < allParameterValues.length;
98                     parameterIndex++) {
99                 Object[][] parameterValues = allParameterValues[parameterIndex];
100 
101                 if (parameterIndex == changingParameterIndex) {
102                     if (changingParameterValue == UNSET) {
103                         // No need to wrap UNSET again
104                         singleTestParams.add(UNSET);
105                     } else {
106                         singleTestParams.add(Optional.of(changingParameterValue));
107                     }
108                 } else {
109                     singleTestParams.add(Optional.of(parameterValues[0][0]));
110                 }
111             }
112             singleTestParams.add(expectFailure);
113             params.add(singleTestParams.toArray());
114         }
115 
116         return params;
117     }
118 
119     /**
120      * Generate test-parameters for all tests.
121      */
122     @Parameterized.Parameters
getParameters()123     public static Collection<Object[]> getParameters() {
124         // {{{valid parameters}, {invalid parameters}}}
125         Object[][][] allParameterValues = {
126          /*mode*/
127                 {{MODE_FULL_INSTALL, MODE_INHERIT_EXISTING}, {0xfff}},
128          /*installLocation*/
129                 {{INSTALL_LOCATION_UNSPECIFIED, INSTALL_LOCATION_AUTO,
130                         INSTALL_LOCATION_INTERNAL_ONLY, INSTALL_LOCATION_PREFER_EXTERNAL,
131                         /* parame is not verified */ 0xfff}, {}},
132          /*size*/
133                 {{1, 8092, Integer.MAX_VALUE, /* parame is not verified */ -1, 0}, {}},
134          /*appPackageName*/
135                 {{"a.package.name", null, /* param is not verified */ "android"}, {}},
136          /*appIcon*/
137                 {{null, Bitmap.createBitmap(42, 42, Bitmap.Config.ARGB_8888)}, {}},
138          /*appLabel*/
139                 {{"A label", null}, {}},
140          /*originatingUri*/
141                 {{Uri.parse("android.com"), null}, {}},
142          /*originatingUid*/
143                 {{-1, 0, 1}, {}},
144          /*referredUri*/
145                 {{Uri.parse("android.com"), null}, {}},
146          /*installReason*/
147                 {{INSTALL_REASON_UNKNOWN, INSTALL_REASON_POLICY, INSTALL_REASON_DEVICE_RESTORE,
148                         INSTALL_REASON_DEVICE_SETUP, INSTALL_REASON_USER,
149                         /* parame is not verified */ 0xfff}, {}}};
150 
151         ArrayList<Object[]> allTestParams = new ArrayList<>();
152 
153         // changingParameterIndex is the index the parameter that changes (0 = mode ...)
154         for (int changingParameterIndex = 0; changingParameterIndex < allParameterValues.length;
155                 changingParameterIndex++) {
156             // Allowed values
157             allTestParams.addAll(getSingleParameterChangingTests(allParameterValues,
158                     changingParameterIndex, allParameterValues[changingParameterIndex][0], false));
159 
160             // Value unset (mode param cannot be unset)
161             if (changingParameterIndex != 0) {
162                 Object[] unset = {UNSET};
163                 allTestParams.addAll(getSingleParameterChangingTests(allParameterValues,
164                         changingParameterIndex, unset, false));
165             }
166 
167             // Illegal values
168             allTestParams.addAll(getSingleParameterChangingTests(allParameterValues,
169                     changingParameterIndex, allParameterValues[changingParameterIndex][1], true));
170         }
171 
172         return allTestParams;
173     }
174 
175     /**
176      * Get the sessionInfo if this package owns the session.
177      *
178      * @param sessionId The id of the session
179      *
180      * @return The {@link PackageInstaller.SessionInfo} object, or {@code null} if session is not
181      * owned by the this package.
182      */
getSessionInfo(int sessionId)183     private SessionInfo getSessionInfo(int sessionId) {
184         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
185         PackageInstaller installer = context.getPackageManager().getPackageInstaller();
186         List<SessionInfo> mySessionInfos = installer.getMySessions();
187 
188         for (SessionInfo sessionInfo : mySessionInfos) {
189             if (sessionInfo.getSessionId() == sessionId) {
190                 return sessionInfo;
191             }
192         }
193 
194         return null;
195     }
196 
197     /**
198      * Create a new installer session.
199      *
200      * @return The new session
201      */
createSession(SessionParams params)202     private int createSession(SessionParams params) throws Exception {
203         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
204         PackageInstaller installer = context.getPackageManager().getPackageInstaller();
205 
206         return installer.createSession(params);
207     }
208 
209     @Test
checkSessionParams()210     public void checkSessionParams() throws Exception {
211         Log.i(LOG_TAG, "mode=" + mode + " installLocation=" + installLocation + " size=" + size
212                 + " appPackageName=" + appPackageName + " appIcon=" + appIcon + " appLabel="
213                 + appLabel + " originatingUri=" + originatingUri + " originatingUid="
214                 + originatingUid + " referredUri=" + referredUri + " installReason=" + installReason
215                 + " expectFailure=" + expectFailure);
216 
217         SessionParams params = new SessionParams(mode.get());
218         installLocation.ifPresent(params::setInstallLocation);
219         size.ifPresent(params::setSize);
220         appPackageName.ifPresent(params::setAppPackageName);
221         appIcon.ifPresent(params::setAppIcon);
222         appLabel.ifPresent(params::setAppLabel);
223         originatingUri.ifPresent(params::setOriginatingUri);
224         originatingUid.ifPresent(params::setOriginatingUid);
225         referredUri.ifPresent(params::setReferrerUri);
226         installReason.ifPresent(params::setInstallReason);
227 
228         int sessionId;
229         try {
230             sessionId = createSession(params);
231 
232             if (expectFailure) {
233                 fail("Creating session did not fail");
234             }
235         } catch (Exception e) {
236             if (expectFailure) {
237                 return;
238             }
239 
240             throw e;
241         }
242 
243         SessionInfo info = getSessionInfo(sessionId);
244 
245         assertThat(info.getMode()).isEqualTo(mode.get());
246         installLocation.ifPresent(i -> assertThat(info.getInstallLocation()).isEqualTo(i));
247         size.ifPresent(i -> assertThat(info.getSize()).isEqualTo(i));
248         appPackageName.ifPresent(s -> assertThat(info.getAppPackageName()).isEqualTo(s));
249 
250         if (appIcon.isPresent()) {
251             if (appIcon.get() == null) {
252                 assertThat(info.getAppIcon()).isNull();
253             } else {
254                 assertThat(appIcon.get().sameAs(info.getAppIcon())).isTrue();
255             }
256         }
257 
258         appLabel.ifPresent(s -> assertThat(info.getAppLabel()).isEqualTo(s));
259         originatingUri.ifPresent(uri -> assertThat(info.getOriginatingUri()).isEqualTo(uri));
260         originatingUid.ifPresent(i -> assertThat(info.getOriginatingUid()).isEqualTo(i));
261         referredUri.ifPresent(uri -> assertThat(info.getReferrerUri()).isEqualTo(uri));
262         installReason.ifPresent(i -> assertThat(info.getInstallReason()).isEqualTo(i));
263     }
264 
265     /** Similar to java.util.Optional but distinguishing between null and unset */
266     private static class Optional<T> {
267         private final boolean mIsSet;
268         private final T mValue;
269 
Optional(boolean isSet, T value)270         Optional(boolean isSet, T value) {
271             mIsSet = isSet;
272             mValue = value;
273         }
274 
of(T value)275         static <T> Optional of(T value) {
276             return new Optional(true, value);
277         }
278 
get()279         T get() {
280             if (!mIsSet) {
281                 throw new IllegalStateException(this + " is not set");
282             }
283             return mValue;
284         }
285 
toString()286         public String toString() {
287             if (!mIsSet) {
288                 return "unset";
289             } else if (mValue == null) {
290                 return "null";
291             } else {
292                 return mValue.toString();
293             }
294         }
295 
isPresent()296         boolean isPresent() {
297             return mIsSet;
298         }
299 
ifPresent(Consumer<T> consumer)300         void ifPresent(Consumer<T> consumer) {
301             if (mIsSet) {
302                 consumer.accept(mValue);
303             }
304         }
305     }
306 }
307