summaryrefslogtreecommitdiff
path: root/tests/tests/permission2/src/android/permission2/cts/RestrictedStoragePermissionSharedUidTest.java
blob: 044abe257b999e0bc471eb88f2c8070653adb4e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.permission2.cts;

import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
import static android.permission.cts.PermissionUtils.isGranted;
import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.DENIED;
import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.ISOLATED;
import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.NON_ISOLATED;

import static com.android.compatibility.common.util.SystemUtil.eventually;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;

import static com.google.common.truth.Truth.assertThat;

import static java.lang.Integer.min;

import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

import java.util.ArrayList;

@AppModeFull(reason = "Instant apps cannot access other app's properties")
@RunWith(Parameterized.class)
public class RestrictedStoragePermissionSharedUidTest {
    private static final String LOG_TAG =
            RestrictedStoragePermissionSharedUidTest.class.getSimpleName();

    public enum StorageState {
        /** The app has non-isolated storage */
        NON_ISOLATED,

        /** The app has isolated storage */
        ISOLATED,

        /** The read-external-storage permission cannot be granted */
        DENIED
    }

    /**
     * An app that is tested
     */
    private static class TestApp {
        private static @NonNull Context sContext =
                InstrumentationRegistry.getInstrumentation().getContext();
        private static @NonNull AppOpsManager sAppOpsManager =
                sContext.getSystemService(AppOpsManager.class);
        private static @NonNull PackageManager sPackageManager = sContext.getPackageManager();

        private final String mApk;
        private final String mPkg;

        public final boolean isRestricted;
        public final boolean hasRequestedLegacyExternalStorage;

        TestApp(@NonNull String apk, @NonNull String pkg, boolean isRestricted,
                @NonNull boolean hasRequestedLegacyExternalStorage) {
            mApk = apk;
            mPkg = pkg;

            this.isRestricted = isRestricted;
            this.hasRequestedLegacyExternalStorage = hasRequestedLegacyExternalStorage;
        }

        /**
         * Assert that the read-external-storage permission was granted or not granted.
         *
         * @param expectGranted {@code true} if the permission is expected to be granted
         */
        void assertStoragePermGranted(boolean expectGranted) {
            eventually(() -> assertThat(isGranted(mPkg, READ_EXTERNAL_STORAGE)).named(
                    this + " read storage granted").isEqualTo(expectGranted));
        }

        /**
         * Assert that the app has non-isolated storage
         *
         * @param expectGranted {@code true} if the app is expected to have non-isolated storage
         */
        void assertHasNotIsolatedStorage(boolean expectHasNotIsolatedStorage) {
            eventually(() -> runWithShellPermissionIdentity(() -> {
                int uid = sContext.getPackageManager().getPackageUid(mPkg, 0);
                if (expectHasNotIsolatedStorage) {
                    assertThat(sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
                            mPkg)).named(this + " legacy storage mode").isEqualTo(MODE_ALLOWED);
                } else {
                    assertThat(sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
                            mPkg)).named(this + " legacy storage mode").isNotEqualTo(MODE_ALLOWED);
                }
            }));
        }

        int getTargetSDK() throws Exception {
            return sPackageManager.getApplicationInfo(mPkg, 0).targetSdkVersion;
        }

        void install() {
            if (isRestricted) {
                runShellCommand("pm install -g --force-queryable --restrict-permissions " + mApk);
            } else {
                runShellCommand("pm install -g --force-queryable " + mApk);
            }
        }

        void uninstall() {
            runShellCommand("pm uninstall " + mPkg);
        }

        @Override
        public String toString() {
            return mPkg.substring(PKG_PREFIX.length());
        }
    }

    /**
     * Placeholder for "no app". The properties are chosen that when combined with another app, the
     * other app always decides the resulting property,
     */
    private static class NoApp extends TestApp {
        NoApp() {
            super("", PKG_PREFIX + "(none)", true, false);
        }

        void assertStoragePermGranted(boolean ignored) {
            // empty
        }

        void assertHasNotIsolatedStorage(boolean ignored) {
            // empty
        }

        @Override
        int getTargetSDK() {
            return 10000;
        }

        @Override
        public void install() {
            // empty
        }

        @Override
        public void uninstall() {
            // empty
        }
    }

    private static final String APK_PATH = "/data/local/tmp/cts/permissions2/";
    private static final String PKG_PREFIX = "android.permission2.cts.legacystoragewithshareduid.";

    private static final TestApp[] TEST_APPS = new TestApp[]{
            new TestApp(APK_PATH + "CtsLegacyStorageNotIsolatedWithSharedUid.apk",
                    PKG_PREFIX + "notisolated", false, true),
            new TestApp(APK_PATH + "CtsLegacyStorageIsolatedWithSharedUid.apk",
                    PKG_PREFIX + "isolated", false, false),
            new TestApp(APK_PATH + "CtsLegacyStorageRestrictedWithSharedUid.apk",
                    PKG_PREFIX + "restricted", true, false),
            new TestApp(APK_PATH + "CtsLegacyStorageRestrictedSdk28WithSharedUid.apk",
                    PKG_PREFIX + "restrictedsdk28", true, true),
            new NoApp()};

    /**
     * First app to be tested. This is the first in an entry created by {@link
     * #getTestAppCombinations}
     */
    @Parameter(0)
    public @NonNull TestApp app1;

    /**
     * Second app to be tested. This is the second in an entry created by {@link
     * #getTestAppCombinations}
     */
    @Parameter(1)
    public @NonNull TestApp app2;

    /**
     * Run this test for all combination of two tests-apps out of {@link #TEST_APPS}. This includes
     * the {@link NoApp}, i.e. we also test a single test-app by itself.
     *
     * @return All combinations of two test-apps
     */
    @Parameters(name = "{0} and {1}")
    public static Iterable<Object[]> getTestAppCombinations() {
        ArrayList<Object[]> parameters = new ArrayList<>();

        for (int firstApp = 0; firstApp < TEST_APPS.length; firstApp++) {
            for (int secondApp = firstApp + 1; secondApp < TEST_APPS.length; secondApp++) {
                parameters.add(new Object[]{TEST_APPS[firstApp], TEST_APPS[secondApp]});
            }
        }

        return parameters;
    }

    @Test
    public void checkExceptedStorageStateForAppsSharingUid() throws Exception {
        app1.install();
        app2.install();

        int targetSDK = min(app1.getTargetSDK(), app2.getTargetSDK());
        boolean isRestricted = app1.isRestricted && app2.isRestricted;
        boolean hasRequestedLegacyExternalStorage =
                app1.hasRequestedLegacyExternalStorage || app2.hasRequestedLegacyExternalStorage;

        StorageState expectedState;
        if (isRestricted) {
            if (targetSDK < Build.VERSION_CODES.Q) {
                expectedState = DENIED;
            } else {
                expectedState = ISOLATED;
            }
        } else if (hasRequestedLegacyExternalStorage && targetSDK <= Build.VERSION_CODES.Q) {
            expectedState = NON_ISOLATED;
        } else {
            expectedState = ISOLATED;
        }

        Log.i(LOG_TAG, "Expected state=" + expectedState);

        app1.assertStoragePermGranted(expectedState != DENIED);
        app2.assertStoragePermGranted(expectedState != DENIED);

        if (expectedState != DENIED) {
            app1.assertHasNotIsolatedStorage(expectedState == NON_ISOLATED);
            app2.assertHasNotIsolatedStorage(expectedState == NON_ISOLATED);
        }
    }

    @After
    public void uninstallAllTestPackages() {
        app1.uninstall();
        app2.uninstall();
    }
}