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