1 /* 2 * Copyright (C) 2017 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.tvprovider.media.tv; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.database.sqlite.SQLiteException; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.media.tv.TvContract; 25 import android.net.Uri; 26 import android.util.Log; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.WorkerThread; 30 31 import java.io.FileNotFoundException; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.net.HttpURLConnection; 36 import java.net.URL; 37 import java.net.URLConnection; 38 39 /** A utility class for conveniently storing and loading channel logos. */ 40 @WorkerThread 41 public class ChannelLogoUtils { 42 private static final String TAG = "ChannelLogoUtils"; 43 44 private static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec 45 private static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec 46 47 /** 48 * Stores channel logo in the system content provider from the given URI. The method will try 49 * to fetch the image file and decode it into {@link Bitmap}. Once the image is successfully 50 * fetched, it will be stored in the system content provider and associated with the given 51 * channel ID. 52 * 53 * <p>The URI provided to this method can be a URL referring to a image file residing in some 54 * remote site/server, or a URI in one of the following formats: 55 * 56 * <ul> 57 * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> 58 * <li>android.resource ({@link android.content.ContentResolver 59 * #SCHEME_ANDROID_RESOURCE})</li> 60 * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> 61 * </ul> 62 * 63 * <p>This method should be run in a worker thread since it may require network connection, 64 * which will raise an exception if it's running in the main thread. 65 * 66 * @param context the context used to access the system content provider 67 * @param channelId the ID of the target channel with which the fetched logo should be 68 * associated 69 * @param logoUri the {@link Uri} of the logo file to be fetched and stored in the system 70 * provider 71 * 72 * @return {@code true} if successfully fetched the image file referred by the give logo URI 73 * and stored it in the system content provider, or {@code false} if failed. 74 * 75 * @see #loadChannelLogo(Context, long) 76 */ 77 public static boolean storeChannelLogo(@NonNull Context context, long channelId, 78 @NonNull Uri logoUri) { 79 String scheme = logoUri.normalizeScheme().getScheme(); 80 URLConnection urlConnection = null; 81 InputStream inputStream = null; 82 Bitmap fetchedLogo = null; 83 try { 84 if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme) 85 || ContentResolver.SCHEME_FILE.equals(scheme) 86 || ContentResolver.SCHEME_CONTENT.equals(scheme)) { 87 // A local resource 88 inputStream = context.getContentResolver().openInputStream(logoUri); 89 } else { 90 // A remote resource, should be an valid URL. 91 urlConnection = getUrlConnection(logoUri.toString()); 92 inputStream = urlConnection.getInputStream(); 93 } 94 fetchedLogo = BitmapFactory.decodeStream(inputStream); 95 } catch (IOException e) { 96 Log.i(TAG, "Failed to get logo from the URI: " + logoUri + "\n", e); 97 } finally { 98 if (inputStream != null) { 99 try { 100 inputStream.close(); 101 } catch (IOException e) { 102 // Do nothing. 103 } 104 } 105 if (urlConnection instanceof HttpURLConnection) { 106 ((HttpURLConnection) urlConnection).disconnect(); 107 } 108 } 109 return fetchedLogo != null && storeChannelLogo(context, channelId, fetchedLogo); 110 } 111 112 /** 113 * Stores the given channel logo {@link Bitmap} in the system content provider and associate 114 * it with the given channel ID. 115 * 116 * @param context the context used to access the system content provider 117 * @param channelId the ID of the target channel with which the given logo should be associated 118 * @param logo the logo image to be stored 119 * 120 * @return {@code true} if successfully stored the logo in the system content provider, 121 * otherwise {@code false}. 122 * 123 * @see #loadChannelLogo(Context, long) 124 */ 125 public static boolean storeChannelLogo(@NonNull Context context, long channelId, 126 @NonNull Bitmap logo) { 127 boolean result = false; 128 Uri localUri = TvContract.buildChannelLogoUri(channelId); 129 try (OutputStream outputStream = context.getContentResolver().openOutputStream(localUri)) { 130 result = logo.compress(Bitmap.CompressFormat.PNG, 100, outputStream); 131 outputStream.flush(); 132 } catch (SQLiteException | IOException e) { 133 Log.i(TAG, "Failed to store the logo to the system content provider.\n", e); 134 } 135 return result; 136 } 137 138 /** 139 * A convenient helper method to get the channel logo associated to the given channel ID from 140 * the system content provider. 141 * 142 * @param context the context used to access the system content provider 143 * @param channelId the ID of the channel whose logo is supposed to be loaded 144 * 145 * @return the requested channel logo in {@link Bitmap}, or {@code null} if not available. 146 * 147 * @see #storeChannelLogo(Context, long, Uri) 148 * @see #storeChannelLogo(Context, long, Bitmap) 149 */ 150 public static Bitmap loadChannelLogo(@NonNull Context context, long channelId) { 151 Bitmap channelLogo = null; 152 try { 153 channelLogo = BitmapFactory.decodeStream(context.getContentResolver().openInputStream( 154 TvContract.buildChannelLogoUri(channelId))); 155 } catch (FileNotFoundException e) { 156 // Channel logo is not found in the content provider. 157 Log.i(TAG, "Channel logo for channel (ID:" + channelId + ") not found.", e); 158 } 159 return channelLogo; 160 } 161 162 private static URLConnection getUrlConnection(String uriString) throws IOException { 163 URLConnection urlConnection = new URL(uriString).openConnection(); 164 urlConnection.setConnectTimeout(CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION); 165 urlConnection.setReadTimeout(READ_TIMEOUT_MS_FOR_URLCONNECTION); 166 return urlConnection; 167 } 168 169 /** @deprecated This type should not be instantiated as it contains only static methods. */ 170 @Deprecated 171 @SuppressWarnings("PrivateConstructorForUtilityClass") 172 public ChannelLogoUtils() { 173 } 174 } 175