1 /*
2  * Copyright (C) 2021 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.providers.media.cloudproviders;
18 
19 import static android.provider.CloudMediaProviderContract.EXTRA_PAGE_TOKEN;
20 
21 import static com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator;
22 
23 import static java.util.concurrent.TimeUnit.MILLISECONDS;
24 
25 import android.content.res.AssetFileDescriptor;
26 import android.database.Cursor;
27 import android.graphics.Point;
28 import android.os.Bundle;
29 import android.os.CancellationSignal;
30 import android.os.ParcelFileDescriptor;
31 import android.provider.CloudMediaProvider;
32 import android.util.Log;
33 
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.providers.media.PickerProviderMediaGenerator;
37 import com.android.providers.media.photopicker.data.CloudProviderQueryExtras;
38 
39 import java.io.FileNotFoundException;
40 
41 /**
42  * Implements a cloud {@link CloudMediaProvider} interface over items generated with {@link
43  * MediaGenerator}
44  *
45  * <p>This provider is intentionally very flaky and will throw a {@link RuntimeException} for two
46  * out of every three requests.
47  */
48 public class FlakyCloudProvider extends CloudMediaProvider {
49     private static final String TAG = "FlakyCloudProvider";
50     public static final String AUTHORITY =
51             "com.android.providers.media.photopicker.tests.cloud_flaky";
52     public static final String ACCOUNT_NAME = "test_account@flakyCloudProvider";
53     private static final int INITIAL_REQUEST_COUNT = 0;
54     private static final int REQUEST_COUNT_FOR_NEXT_ONE_TO_FLAKE = 2;
55 
56     private final MediaGenerator mMediaGenerator =
57             PickerProviderMediaGenerator.getMediaGenerator(AUTHORITY);
58 
59     private int mRequestCount = INITIAL_REQUEST_COUNT;
60 
61     /** Determines if the current request should flake. */
shouldFlake()62     private boolean shouldFlake() {
63 
64         // Always succeed on the first request.
65         if (++mRequestCount < REQUEST_COUNT_FOR_NEXT_ONE_TO_FLAKE) {
66             return false;
67         }
68 
69         if (mRequestCount > REQUEST_COUNT_FOR_NEXT_ONE_TO_FLAKE) {
70             mRequestCount = INITIAL_REQUEST_COUNT;
71         }
72 
73         return true;
74     }
75 
76     @Override
onCreate()77     public boolean onCreate() {
78         mMediaGenerator.setAccountInfo(ACCOUNT_NAME, /* configIntent= */ null);
79         return true;
80     }
81 
82     @Override
onQueryMedia(Bundle extras)83     public Cursor onQueryMedia(Bundle extras) {
84         final CloudProviderQueryExtras queryExtras =
85                 CloudProviderQueryExtras.fromCloudMediaBundle(extras);
86 
87         if (shouldFlake()) {
88             throw new RuntimeException("Simulating a crash in FlakyCloudProvider onQueryMedia");
89         }
90 
91         String pageToken = extras.getString(EXTRA_PAGE_TOKEN, null);
92 
93         return mMediaGenerator.getMedia(
94                 queryExtras.getGeneration(),
95                 queryExtras.getAlbumId(),
96                 queryExtras.getMimeTypes(),
97                 queryExtras.getSizeBytes(),
98                 pageToken,
99                 queryExtras.getPageSize());
100     }
101 
102     @Override
onQueryDeletedMedia(Bundle extras)103     public Cursor onQueryDeletedMedia(Bundle extras) {
104         final CloudProviderQueryExtras queryExtras =
105                 CloudProviderQueryExtras.fromCloudMediaBundle(extras);
106 
107         if (shouldFlake()) {
108             throw new RuntimeException(
109                     "Simulating a crash in FlakyCloudProvider onQueryDeletedMedia");
110         }
111 
112         String pageToken = extras.getString(EXTRA_PAGE_TOKEN, null);
113 
114         return mMediaGenerator.getDeletedMedia(queryExtras.getGeneration(), pageToken);
115     }
116 
117     @Override
onQueryAlbums(Bundle extras)118     public Cursor onQueryAlbums(Bundle extras) {
119         final CloudProviderQueryExtras queryExtras =
120                 CloudProviderQueryExtras.fromCloudMediaBundle(extras);
121 
122         if (shouldFlake()) {
123             throw new RuntimeException("Simulating a crash in FlakyCloudProvider onQueryAlbums");
124         }
125 
126         String pageToken = extras.getString(EXTRA_PAGE_TOKEN, null);
127 
128         return mMediaGenerator.getAlbums(
129                 queryExtras.getMimeTypes(),
130                 queryExtras.getSizeBytes(), /* isLocal */
131                 false,
132                 pageToken);
133     }
134 
135     @Override
onOpenPreview( String mediaId, Point size, Bundle extras, CancellationSignal signal)136     public AssetFileDescriptor onOpenPreview(
137             String mediaId, Point size, Bundle extras, CancellationSignal signal)
138             throws FileNotFoundException {
139         throw new UnsupportedOperationException("onOpenPreview not supported");
140     }
141 
142     @Override
onOpenMedia( String mediaId, Bundle extras, CancellationSignal signal)143     public ParcelFileDescriptor onOpenMedia(
144             String mediaId, Bundle extras, CancellationSignal signal) throws FileNotFoundException {
145         throw new UnsupportedOperationException("onOpenMedia not supported");
146     }
147 
148     @Override
onGetMediaCollectionInfo(Bundle extras)149     public Bundle onGetMediaCollectionInfo(Bundle extras) {
150         if (shouldFlake()) {
151             try {
152                 MILLISECONDS.sleep(/* timeout= */ 200L);
153             } catch (InterruptedException e) {
154                 Log.d(TAG, "Error while sleep when should flake on get media collection info.", e);
155             }
156         }
157 
158         return mMediaGenerator.getMediaCollectionInfo();
159     }
160 
161     @VisibleForTesting
resetToNotFlakeInTheNextRequest()162     public void resetToNotFlakeInTheNextRequest() {
163         mRequestCount = INITIAL_REQUEST_COUNT;
164     }
165 
166     @VisibleForTesting
setToFlakeInTheNextRequest()167     public void setToFlakeInTheNextRequest() {
168         mRequestCount = REQUEST_COUNT_FOR_NEXT_ONE_TO_FLAKE;
169     }
170 }
171