1 /*
2  * Copyright (C) 2018 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.provider.cts.media;
18 
19 import static android.provider.cts.media.MediaStoreTest.TAG;
20 
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.database.ContentObserver;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Build;
29 import android.os.Handler;
30 import android.os.HandlerThread;
31 import android.provider.MediaStore;
32 import android.provider.cts.ProviderTestUtils;
33 import android.util.Log;
34 
35 import androidx.test.InstrumentationRegistry;
36 import androidx.test.filters.SdkSuppress;
37 
38 import org.junit.Before;
39 import org.junit.Ignore;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.Parameterized;
43 import org.junit.runners.Parameterized.Parameter;
44 import org.junit.runners.Parameterized.Parameters;
45 
46 import java.util.concurrent.CountDownLatch;
47 import java.util.concurrent.TimeUnit;
48 
49 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
50 @RunWith(Parameterized.class)
51 public class MediaStoreNotificationTest {
52     private Context mContext;
53     private ContentResolver mResolver;
54 
55     private Uri mSpecificImages;
56     private Uri mSpecificFiles;
57     private Uri mGenericImages;
58     private Uri mGenericFiles;
59 
60     @Parameter(0)
61     public String mVolumeName;
62 
63     @Parameters
data()64     public static Iterable<? extends Object> data() {
65         return ProviderTestUtils.getSharedVolumeNames();
66     }
67 
68     @Before
setUp()69     public void setUp() throws Exception {
70         mContext = InstrumentationRegistry.getTargetContext();
71         mResolver = mContext.getContentResolver();
72 
73         Log.d(TAG, "Using volume " + mVolumeName);
74         mSpecificImages = MediaStore.Images.Media.getContentUri(mVolumeName);
75         mSpecificFiles = MediaStore.Files.getContentUri(mVolumeName);
76         mGenericImages = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
77         mGenericFiles = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
78     }
79 
80     @Test
testSimple()81     public void testSimple() throws Exception {
82         Uri specificImage;
83         Uri specificFile;
84         Uri genericImage;
85         Uri genericFile;
86 
87         try (BlockingObserver si = BlockingObserver.createAndRegister(mSpecificImages);
88                 BlockingObserver sf = BlockingObserver.createAndRegister(mSpecificFiles);
89                 BlockingObserver gi = BlockingObserver.createAndRegister(mGenericImages);
90                 BlockingObserver gf = BlockingObserver.createAndRegister(mGenericFiles)) {
91             final ContentValues values = new ContentValues();
92             values.put(MediaStore.Images.Media.IS_PENDING, 1);
93             values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
94             values.put(MediaStore.Images.Media.DISPLAY_NAME, "cts" + System.nanoTime());
95             specificImage = mResolver.insert(mSpecificImages, values);
96 
97             final long id = ContentUris.parseId(specificImage);
98             specificFile = ContentUris.withAppendedId(mSpecificFiles, id);
99             genericImage = ContentUris.withAppendedId(mGenericImages, id);
100             genericFile = ContentUris.withAppendedId(mGenericFiles, id);
101         }
102 
103         try (BlockingObserver si = BlockingObserver.createAndRegister(specificImage);
104                 BlockingObserver sf = BlockingObserver.createAndRegister(specificFile);
105                 BlockingObserver gi = BlockingObserver.createAndRegister(genericImage);
106                 BlockingObserver gf = BlockingObserver.createAndRegister(genericFile)) {
107             final ContentValues values = new ContentValues();
108             values.put(MediaStore.Images.Media.SIZE, 32);
109             mResolver.update(specificImage, values, null, null);
110         }
111 
112         try (BlockingObserver si = BlockingObserver.createAndRegister(specificImage);
113                 BlockingObserver sf = BlockingObserver.createAndRegister(specificFile);
114                 BlockingObserver gi = BlockingObserver.createAndRegister(genericImage);
115                 BlockingObserver gf = BlockingObserver.createAndRegister(genericFile)) {
116             mResolver.delete(specificImage, null, null);
117         }
118     }
119 
120     @Test
testCursor()121     public void testCursor() throws Exception {
122         try (Cursor si = mResolver.query(mSpecificImages, null, null, null);
123                 Cursor sf = mResolver.query(mSpecificFiles, null, null, null);
124                 Cursor gi = mResolver.query(mGenericImages, null, null, null);
125                 Cursor gf = mResolver.query(mGenericFiles, null, null, null)) {
126             try (BlockingObserver sio = BlockingObserver.create();
127                     BlockingObserver sfo = BlockingObserver.create();
128                     BlockingObserver gio = BlockingObserver.create();
129                     BlockingObserver gfo = BlockingObserver.create()) {
130                 si.registerContentObserver(sio);
131                 sf.registerContentObserver(sfo);
132                 gi.registerContentObserver(gio);
133                 gf.registerContentObserver(gfo);
134 
135                 // Insert a simple item that will trigger notifications
136                 final ContentValues values = new ContentValues();
137                 values.put(MediaStore.Images.Media.IS_PENDING, 1);
138                 values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
139                 values.put(MediaStore.Images.Media.DISPLAY_NAME, "cts" + System.nanoTime());
140                 final Uri uri = mResolver.insert(mSpecificImages, values);
141                 mResolver.delete(uri, null, null);
142             }
143         }
144     }
145 
146     private static class BlockingObserver extends ContentObserver implements AutoCloseable {
147         private final Uri mUri;
148         private final CountDownLatch mLatch = new CountDownLatch(1);
149 
150         private static HandlerThread sHandlerThread;
151         private static Handler sHandler;
152 
153         static {
154             sHandlerThread = new HandlerThread(TAG);
sHandlerThread.start()155             sHandlerThread.start();
156             sHandler = new Handler(sHandlerThread.getLooper());
157         }
158 
BlockingObserver(Uri uri)159         private BlockingObserver(Uri uri) {
160             super(sHandler);
161             mUri = uri;
162         }
163 
create()164         public static BlockingObserver create() {
165             return new BlockingObserver(null);
166         }
167 
createAndRegister(Uri uri)168         public static BlockingObserver createAndRegister(Uri uri) {
169             final BlockingObserver obs = new BlockingObserver(uri);
170             InstrumentationRegistry.getTargetContext().getContentResolver()
171                     .registerContentObserver(uri, true, obs);
172             return obs;
173         }
174 
175         @Override
onChange(boolean selfChange, Uri uri)176         public void onChange(boolean selfChange, Uri uri) {
177             super.onChange(selfChange, uri);
178             Log.v(TAG, "Notified about " + uri);
179             mLatch.countDown();
180         }
181 
182         @Override
close()183         public void close() {
184             try {
185                 if (!mLatch.await(5, TimeUnit.SECONDS)) {
186                     throw new InterruptedException();
187                 }
188             } catch (InterruptedException e) {
189                 throw new IllegalStateException("Failed to get notification for " + mUri);
190             } finally {
191                 InstrumentationRegistry.getTargetContext().getContentResolver()
192                         .unregisterContentObserver(this);
193             }
194         }
195     }
196 }
197