1 /* 2 * Copyright 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 androidx.media; 18 19 import static org.junit.Assert.assertEquals; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.os.Bundle; 24 import android.util.Log; 25 26 import androidx.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback; 27 import androidx.media.MediaSession2.ControllerInfo; 28 import androidx.media.MediaSession2.SessionCallback; 29 import androidx.media.TestUtils.SyncHandler; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.concurrent.Executor; 34 import java.util.concurrent.Executors; 35 import java.util.concurrent.TimeUnit; 36 37 import javax.annotation.concurrent.GuardedBy; 38 39 /** 40 * Mock implementation of {@link MediaLibraryService2} for testing. 41 */ 42 public class MockMediaLibraryService2 extends MediaLibraryService2 { 43 // Keep in sync with the AndroidManifest.xml 44 public static final String ID = "TestLibrary"; 45 46 public static final String ROOT_ID = "rootId"; 47 public static final Bundle EXTRAS = new Bundle(); 48 49 public static final String MEDIA_ID_GET_ITEM = "media_id_get_item"; 50 51 public static final String PARENT_ID = "parent_id"; 52 public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children"; 53 public static final String PARENT_ID_ERROR = "parent_id_error"; 54 55 public static final List<MediaItem2> GET_CHILDREN_RESULT = new ArrayList<>(); 56 public static final int CHILDREN_COUNT = 100; 57 58 public static final String SEARCH_QUERY = "search_query"; 59 public static final String SEARCH_QUERY_TAKES_TIME = "search_query_takes_time"; 60 public static final int SEARCH_TIME_IN_MS = 5000; 61 public static final String SEARCH_QUERY_EMPTY_RESULT = "search_query_empty_result"; 62 63 public static final List<MediaItem2> SEARCH_RESULT = new ArrayList<>(); 64 public static final int SEARCH_RESULT_COUNT = 50; 65 66 // TODO(jaewan): Uncomment here after DataSourceDesc.builder is ready. 67 // private static final DataSourceDesc DATA_SOURCE_DESC = 68 // new DataSourceDesc.Builder().setDataSource(new FileDescriptor()).build(); 69 private static final DataSourceDesc DATA_SOURCE_DESC = null; 70 71 private static final String TAG = "MockMediaLibrarySvc2"; 72 73 static { 74 EXTRAS.putString(ROOT_ID, ROOT_ID); 75 } 76 @GuardedBy("MockMediaLibraryService2.class") 77 private static SessionToken2 sToken; 78 79 private MediaLibrarySession mSession; 80 81 public MockMediaLibraryService2() { 82 super(); 83 GET_CHILDREN_RESULT.clear(); 84 String getChildrenMediaIdPrefix = "get_children_media_id_"; 85 for (int i = 0; i < CHILDREN_COUNT; i++) { 86 GET_CHILDREN_RESULT.add(createMediaItem(getChildrenMediaIdPrefix + i)); 87 } 88 89 SEARCH_RESULT.clear(); 90 String getSearchResultMediaIdPrefix = "get_search_result_media_id_"; 91 for (int i = 0; i < SEARCH_RESULT_COUNT; i++) { 92 SEARCH_RESULT.add(createMediaItem(getSearchResultMediaIdPrefix + i)); 93 } 94 } 95 96 @Override 97 public void onCreate() { 98 TestServiceRegistry.getInstance().setServiceInstance(this); 99 super.onCreate(); 100 } 101 102 @Override 103 public MediaLibrarySession onCreateSession(String sessionId) { 104 final MockPlayer player = new MockPlayer(1); 105 final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler(); 106 final Executor executor = new Executor() { 107 @Override 108 public void execute(Runnable runnable) { 109 handler.post(runnable); 110 } 111 }; 112 SessionCallback callback = TestServiceRegistry.getInstance().getSessionCallback(); 113 MediaLibrarySessionCallback librarySessionCallback; 114 if (callback instanceof MediaLibrarySessionCallback) { 115 librarySessionCallback = (MediaLibrarySessionCallback) callback; 116 } else { 117 // Callback hasn't set. Use default callback 118 librarySessionCallback = new TestLibrarySessionCallback(); 119 } 120 mSession = new MediaLibrarySession.Builder(MockMediaLibraryService2.this, executor, 121 librarySessionCallback).setPlayer(player).setId(sessionId).build(); 122 return mSession; 123 } 124 125 @Override 126 public void onDestroy() { 127 super.onDestroy(); 128 TestServiceRegistry.getInstance().cleanUp(); 129 } 130 131 public static SessionToken2 getToken(Context context) { 132 synchronized (MockMediaLibraryService2.class) { 133 if (sToken == null) { 134 sToken = new SessionToken2(context, new ComponentName( 135 context.getPackageName(), MockMediaLibraryService2.class.getName())); 136 assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, sToken.getType()); 137 } 138 return sToken; 139 } 140 } 141 142 private class TestLibrarySessionCallback extends MediaLibrarySessionCallback { 143 @Override 144 public LibraryRoot onGetLibraryRoot(MediaLibrarySession session, ControllerInfo controller, 145 Bundle rootHints) { 146 return new LibraryRoot(ROOT_ID, EXTRAS); 147 } 148 149 @Override 150 public MediaItem2 onGetItem(MediaLibrarySession session, ControllerInfo controller, 151 String mediaId) { 152 if (MEDIA_ID_GET_ITEM.equals(mediaId)) { 153 return createMediaItem(mediaId); 154 } else { 155 return null; 156 } 157 } 158 159 @Override 160 public List<MediaItem2> onGetChildren(MediaLibrarySession session, 161 ControllerInfo controller, String parentId, int page, int pageSize, Bundle extras) { 162 if (PARENT_ID.equals(parentId)) { 163 return getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize); 164 } else if (PARENT_ID_ERROR.equals(parentId)) { 165 return null; 166 } 167 // Includes the case of PARENT_ID_NO_CHILDREN. 168 return new ArrayList<>(); 169 } 170 171 @Override 172 public void onSearch(MediaLibrarySession session, final ControllerInfo controllerInfo, 173 final String query, final Bundle extras) { 174 if (SEARCH_QUERY.equals(query)) { 175 mSession.notifySearchResultChanged(controllerInfo, query, SEARCH_RESULT_COUNT, 176 extras); 177 } else if (SEARCH_QUERY_TAKES_TIME.equals(query)) { 178 // Searching takes some time. Notify after 5 seconds. 179 Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() { 180 @Override 181 public void run() { 182 mSession.notifySearchResultChanged( 183 controllerInfo, query, SEARCH_RESULT_COUNT, extras); 184 } 185 }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS); 186 } else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) { 187 mSession.notifySearchResultChanged(controllerInfo, query, 0, extras); 188 } else { 189 // TODO: For the error case, how should we notify the browser? 190 } 191 } 192 193 @Override 194 public List<MediaItem2> onGetSearchResult(MediaLibrarySession session, 195 ControllerInfo controllerInfo, String query, int page, int pageSize, 196 Bundle extras) { 197 if (SEARCH_QUERY.equals(query)) { 198 return getPaginatedResult(SEARCH_RESULT, page, pageSize); 199 } else { 200 return null; 201 } 202 } 203 } 204 205 private List<MediaItem2> getPaginatedResult(List<MediaItem2> items, int page, int pageSize) { 206 if (items == null) { 207 return null; 208 } else if (items.size() == 0) { 209 return new ArrayList<>(); 210 } 211 212 final int totalItemCount = items.size(); 213 int fromIndex = (page - 1) * pageSize; 214 int toIndex = Math.min(page * pageSize, totalItemCount); 215 216 List<MediaItem2> paginatedResult = new ArrayList<>(); 217 try { 218 // The case of (fromIndex >= totalItemCount) will throw exception below. 219 paginatedResult = items.subList(fromIndex, toIndex); 220 } catch (IndexOutOfBoundsException | IllegalArgumentException ex) { 221 Log.d(TAG, "Result is empty for given pagination arguments: totalItemCount=" 222 + totalItemCount + ", page=" + page + ", pageSize=" + pageSize, ex); 223 } 224 return paginatedResult; 225 } 226 227 private MediaItem2 createMediaItem(String mediaId) { 228 Context context = MockMediaLibraryService2.this; 229 return new MediaItem2.Builder(0 /* Flags */) 230 .setMediaId(mediaId) 231 .setDataSourceDesc(DATA_SOURCE_DESC) 232 .setMetadata(new MediaMetadata2.Builder() 233 .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId) 234 .build()) 235 .build(); 236 } 237 } 238