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.photopicker.data.model; 18 19 import static android.provider.CloudMediaProviderContract.AlbumColumns; 20 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA; 21 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_DOWNLOADS; 22 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES; 23 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_SCREENSHOTS; 24 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS; 25 26 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorInt; 27 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString; 28 29 import android.content.Context; 30 import android.database.Cursor; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.provider.MediaStore; 34 import android.text.TextUtils; 35 import android.util.Log; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.VisibleForTesting; 39 40 import com.android.providers.media.R; 41 import com.android.providers.media.photopicker.data.ItemsProvider; 42 import com.android.providers.media.photopicker.data.glide.GlideLoadable; 43 44 import java.util.List; 45 import java.util.Locale; 46 47 /** 48 * Defines each category (which is group of items) for the photo picker. 49 */ 50 public class Category { 51 public static final String TAG = "PhotoPicker"; 52 public static final Category DEFAULT = new Category(); 53 public static final Category EMPTY_VIEW = new Category("EMPTY_VIEW"); 54 private static final List<String> TRANSLATABLE_CATEGORIES = List.of(ALBUM_ID_VIDEOS, 55 ALBUM_ID_CAMERA, ALBUM_ID_SCREENSHOTS, ALBUM_ID_DOWNLOADS, ALBUM_ID_FAVORITES); 56 57 private final String mId; 58 private final String mAuthority; 59 private final String mDisplayName; 60 private final boolean mIsLocal; 61 private final Uri mCoverUri; 62 private final int mItemCount; 63 Category()64 private Category() { 65 this(null, null, null, null, 0, false); 66 } 67 Category(String id)68 private Category(String id) { 69 this(id, null, null, null, 0, false); 70 } 71 @VisibleForTesting Category(String id, String authority, String displayName, Uri coverUri, int itemCount, boolean isLocal)72 public Category(String id, String authority, String displayName, Uri coverUri, int itemCount, 73 boolean isLocal) { 74 mId = id; 75 mAuthority = authority; 76 mDisplayName = displayName; 77 mIsLocal = isLocal; 78 mCoverUri = coverUri; 79 mItemCount = itemCount; 80 } 81 82 @Override toString()83 public String toString() { 84 return String.format(Locale.ROOT, "Category: {mId: %s, mAuthority: %s, mDisplayName: %s, " + 85 "mCoverUri: %s, mItemCount: %d, mIsLocal: %b", 86 mId, mAuthority, mDisplayName, mCoverUri, mItemCount, mIsLocal); 87 } 88 getId()89 public String getId() { 90 return mId; 91 } 92 getAuthority()93 public String getAuthority() { 94 return mAuthority; 95 } 96 getDisplayName(Context context)97 public String getDisplayName(Context context) { 98 if (TRANSLATABLE_CATEGORIES.contains(mId)) { 99 return getLocalizedDisplayName(context, mId); 100 } 101 return mDisplayName; 102 } 103 isLocal()104 public boolean isLocal() { 105 return mIsLocal; 106 } 107 getCoverUri()108 public Uri getCoverUri() { 109 return mCoverUri; 110 } 111 getItemCount()112 public int getItemCount() { 113 return mItemCount; 114 } 115 isDefault()116 public boolean isDefault() { 117 return TextUtils.isEmpty(mId); 118 } 119 120 /** 121 * Write the {@link Category} to the given {@code bundle}. 122 */ toBundle(@onNull Bundle bundle)123 public void toBundle(@NonNull Bundle bundle) { 124 bundle.putString(AlbumColumns.ID, mId); 125 bundle.putString(AlbumColumns.AUTHORITY, mAuthority); 126 bundle.putString(AlbumColumns.DISPLAY_NAME, mDisplayName); 127 // Re-using the 'media_cover_id' to store the media_cover_uri for lack of 128 // a different constant 129 bundle.putParcelable(AlbumColumns.MEDIA_COVER_ID, mCoverUri); 130 bundle.putInt(AlbumColumns.MEDIA_COUNT, mItemCount); 131 bundle.putBoolean(AlbumColumns.IS_LOCAL, mIsLocal); 132 } 133 134 /** 135 * Create a {@link Category} from the {@code bundle}. 136 */ fromBundle(@onNull Bundle bundle)137 public static Category fromBundle(@NonNull Bundle bundle) { 138 return new Category(bundle.getString(AlbumColumns.ID), 139 bundle.getString(AlbumColumns.AUTHORITY), 140 bundle.getString(AlbumColumns.DISPLAY_NAME), 141 bundle.getParcelable(AlbumColumns.MEDIA_COVER_ID), 142 bundle.getInt(AlbumColumns.MEDIA_COUNT), 143 bundle.getBoolean(AlbumColumns.IS_LOCAL)); 144 } 145 146 /** 147 * Create a {@link Category} from the {@code cursor}. 148 */ fromCursor(@onNull Cursor cursor, @NonNull UserId userId)149 public static Category fromCursor(@NonNull Cursor cursor, @NonNull UserId userId) { 150 String authority = getCursorString(cursor, AlbumColumns.AUTHORITY); 151 if (authority == null) { 152 // Authority will be null for cloud albums in cursor. 153 String cloudProvider = cursor.getExtras().getString(MediaStore.EXTRA_CLOUD_PROVIDER); 154 if (cloudProvider == null) { 155 // If cloud provider is null, cloud albums will not show up properly. 156 Log.e(TAG, "Cloud provider is set by the user but not passed in album media cursor" 157 + " extras."); 158 } else { 159 authority = cloudProvider; 160 } 161 } 162 final boolean isLocal = authority != null 163 && authority.equals(cursor.getExtras().getString(MediaStore.EXTRA_LOCAL_PROVIDER)); 164 final Uri coverUri = ItemsProvider.getItemsUri( 165 getCursorString(cursor, AlbumColumns.MEDIA_COVER_ID), authority, userId); 166 167 return new Category(getCursorString(cursor, AlbumColumns.ID), 168 authority, 169 getCursorString(cursor, AlbumColumns.DISPLAY_NAME), 170 getCursorString(cursor, AlbumColumns.MEDIA_COVER_ID) != null ? coverUri : null, 171 getCursorInt(cursor, AlbumColumns.MEDIA_COUNT), 172 isLocal); 173 } 174 getLocalizedDisplayName(Context context, String albumId)175 private static String getLocalizedDisplayName(Context context, String albumId) { 176 switch (albumId) { 177 case ALBUM_ID_VIDEOS: 178 return context.getString(R.string.picker_category_videos); 179 case ALBUM_ID_CAMERA: 180 return context.getString(R.string.picker_category_camera); 181 case ALBUM_ID_SCREENSHOTS: 182 return context.getString(R.string.picker_category_screenshots); 183 case ALBUM_ID_DOWNLOADS: 184 return context.getString(R.string.picker_category_downloads); 185 case ALBUM_ID_FAVORITES: 186 return context.getString(R.string.picker_category_favorites); 187 default: 188 return albumId; 189 } 190 } 191 192 /** 193 * Convert this category into a loadable object for Glide. 194 * 195 * @return {@link GlideLoadable} that represents the relevant loadable data for this item. 196 */ toGlideLoadable()197 public GlideLoadable toGlideLoadable() { 198 return new GlideLoadable(getCoverUri()); 199 } 200 } 201