| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.photo_picker; | 5 package org.chromium.chrome.browser.photo_picker; |
| 6 | 6 |
| 7 import android.content.ComponentName; | 7 import android.content.ComponentName; |
| 8 import android.content.Context; | 8 import android.content.Context; |
| 9 import android.content.Intent; | 9 import android.content.Intent; |
| 10 import android.content.ServiceConnection; | 10 import android.content.ServiceConnection; |
| 11 import android.graphics.Bitmap; | 11 import android.graphics.Bitmap; |
| 12 import android.os.Bundle; | 12 import android.os.Bundle; |
| 13 import android.os.Handler; | 13 import android.os.Handler; |
| 14 import android.os.IBinder; | 14 import android.os.IBinder; |
| 15 import android.os.Message; | 15 import android.os.Message; |
| 16 import android.os.Messenger; | 16 import android.os.Messenger; |
| 17 import android.os.ParcelFileDescriptor; | 17 import android.os.ParcelFileDescriptor; |
| 18 import android.os.RemoteException; | 18 import android.os.RemoteException; |
| 19 import android.os.StrictMode; | 19 import android.os.StrictMode; |
| 20 import android.os.SystemClock; |
| 20 | 21 |
| 21 import org.chromium.base.Log; | 22 import org.chromium.base.Log; |
| 23 import org.chromium.base.metrics.RecordHistogram; |
| 24 import org.chromium.chrome.browser.util.ConversionUtils; |
| 22 | 25 |
| 23 import java.io.File; | 26 import java.io.File; |
| 24 import java.io.FileDescriptor; | 27 import java.io.FileDescriptor; |
| 25 import java.io.FileInputStream; | 28 import java.io.FileInputStream; |
| 26 import java.io.IOException; | 29 import java.io.IOException; |
| 27 import java.lang.ref.WeakReference; | 30 import java.lang.ref.WeakReference; |
| 28 import java.util.LinkedHashMap; | 31 import java.util.LinkedHashMap; |
| 32 import java.util.concurrent.TimeUnit; |
| 29 | 33 |
| 30 /** | 34 /** |
| 31 * A class to communicate with the {@link DecoderService}. | 35 * A class to communicate with the {@link DecoderService}. |
| 32 */ | 36 */ |
| 33 public class DecoderServiceHost { | 37 public class DecoderServiceHost { |
| 34 // A tag for logging error messages. | 38 // A tag for logging error messages. |
| 35 private static final String TAG = "ImageDecoderHost"; | 39 private static final String TAG = "ImageDecoderHost"; |
| 36 | 40 |
| 37 /** | 41 /** |
| 38 * Interface for notifying clients of the service being ready. | 42 * Interface for notifying clients of the service being ready. |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 86 private static class DecoderServiceParams { | 90 private static class DecoderServiceParams { |
| 87 // The path to the file containing the bitmap to decode. | 91 // The path to the file containing the bitmap to decode. |
| 88 public String mFilePath; | 92 public String mFilePath; |
| 89 | 93 |
| 90 // The requested size (width and height) of the bitmap, once decoded. | 94 // The requested size (width and height) of the bitmap, once decoded. |
| 91 public int mSize; | 95 public int mSize; |
| 92 | 96 |
| 93 // The callback to use to communicate the results of the decoding. | 97 // The callback to use to communicate the results of the decoding. |
| 94 ImageDecodedCallback mCallback; | 98 ImageDecodedCallback mCallback; |
| 95 | 99 |
| 100 // The timestamp for when the request was sent for decoding. |
| 101 long mTimestamp; |
| 102 |
| 96 public DecoderServiceParams(String filePath, int size, ImageDecodedCallb
ack callback) { | 103 public DecoderServiceParams(String filePath, int size, ImageDecodedCallb
ack callback) { |
| 97 mFilePath = filePath; | 104 mFilePath = filePath; |
| 98 mSize = size; | 105 mSize = size; |
| 99 mCallback = callback; | 106 mCallback = callback; |
| 100 } | 107 } |
| 101 } | 108 } |
| 102 | 109 |
| 103 // Map of file paths to decoder parameters in order of request. | 110 // Map of file paths to decoder parameters in order of request. |
| 104 private LinkedHashMap<String, DecoderServiceParams> mRequests = new LinkedHa
shMap<>(); | 111 private LinkedHashMap<String, DecoderServiceParams> mRequests = new LinkedHa
shMap<>(); |
| 105 LinkedHashMap<String, DecoderServiceParams> getRequests() { | 112 LinkedHashMap<String, DecoderServiceParams> getRequests() { |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 162 mRequests.put(filePath, params); | 169 mRequests.put(filePath, params); |
| 163 if (mRequests.size() == 1) dispatchNextDecodeImageRequest(); | 170 if (mRequests.size() == 1) dispatchNextDecodeImageRequest(); |
| 164 } | 171 } |
| 165 | 172 |
| 166 /** | 173 /** |
| 167 * Dispatches the next image for decoding (from the queue). | 174 * Dispatches the next image for decoding (from the queue). |
| 168 */ | 175 */ |
| 169 private void dispatchNextDecodeImageRequest() { | 176 private void dispatchNextDecodeImageRequest() { |
| 170 if (mRequests.entrySet().iterator().hasNext()) { | 177 if (mRequests.entrySet().iterator().hasNext()) { |
| 171 DecoderServiceParams params = mRequests.entrySet().iterator().next()
.getValue(); | 178 DecoderServiceParams params = mRequests.entrySet().iterator().next()
.getValue(); |
| 179 params.mTimestamp = SystemClock.elapsedRealtime(); |
| 172 dispatchDecodeImageRequest(params.mFilePath, params.mSize); | 180 dispatchDecodeImageRequest(params.mFilePath, params.mSize); |
| 173 } | 181 } |
| 174 } | 182 } |
| 175 | 183 |
| 176 /** | 184 /** |
| 177 * Ties up all the loose ends from the decoding request (communicates the re
sults of the | 185 * Ties up all the loose ends from the decoding request (communicates the re
sults of the |
| 178 * decoding process back to the client, and takes care of house-keeping chor
es regarding | 186 * decoding process back to the client, and takes care of house-keeping chor
es regarding |
| 179 * the request queue). | 187 * the request queue). |
| 180 * @param filePath The path to the image that was just decoded. | 188 * @param filePath The path to the image that was just decoded. |
| 181 * @param bitmap The resulting decoded bitmap. | 189 * @param bitmap The resulting decoded bitmap. |
| 190 * @param decodeTime The length of time it took to decode the bitmap. |
| 182 */ | 191 */ |
| 183 public void closeRequest(String filePath, Bitmap bitmap) { | 192 public void closeRequest(String filePath, Bitmap bitmap, long decodeTime) { |
| 184 DecoderServiceParams params = getRequests().get(filePath); | 193 DecoderServiceParams params = getRequests().get(filePath); |
| 185 if (params != null) { | 194 if (params != null) { |
| 195 long endRpcCall = SystemClock.elapsedRealtime(); |
| 196 RecordHistogram.recordTimesHistogram("Android.PhotoPicker.RequestPro
cessTime", |
| 197 endRpcCall - params.mTimestamp, TimeUnit.MILLISECONDS); |
| 198 |
| 186 params.mCallback.imageDecodedCallback(filePath, bitmap); | 199 params.mCallback.imageDecodedCallback(filePath, bitmap); |
| 200 |
| 201 if (decodeTime != -1) { |
| 202 RecordHistogram.recordTimesHistogram( |
| 203 "Android.PhotoPicker.ImageDecodeTime", decodeTime, TimeU
nit.MILLISECONDS); |
| 204 |
| 205 int sizeInKB = bitmap.getByteCount() / ConversionUtils.BYTES_PER
_KILOBYTE; |
| 206 RecordHistogram.recordCustomCountHistogram( |
| 207 "Android.PhotoPicker.ImageByteCount", sizeInKB, 1, 10000
0, 50); |
| 208 } |
| 187 getRequests().remove(filePath); | 209 getRequests().remove(filePath); |
| 188 } | 210 } |
| 189 dispatchNextDecodeImageRequest(); | 211 dispatchNextDecodeImageRequest(); |
| 190 } | 212 } |
| 191 | 213 |
| 192 /** | 214 /** |
| 193 * Communicates with the server to decode a single bitmap. | 215 * Communicates with the server to decode a single bitmap. |
| 194 * @param filePath The path to the image on disk. | 216 * @param filePath The path to the image on disk. |
| 195 * @param size The requested width and height of the resulting bitmap. | 217 * @param size The requested width and height of the resulting bitmap. |
| 196 */ | 218 */ |
| 197 private void dispatchDecodeImageRequest(String filePath, int size) { | 219 private void dispatchDecodeImageRequest(String filePath, int size) { |
| 198 // Obtain a file descriptor to send over to the sandboxed process. | 220 // Obtain a file descriptor to send over to the sandboxed process. |
| 199 File file = new File(filePath); | 221 File file = new File(filePath); |
| 200 FileInputStream inputFile = null; | 222 FileInputStream inputFile = null; |
| 201 ParcelFileDescriptor pfd = null; | 223 ParcelFileDescriptor pfd = null; |
| 202 Bundle bundle = new Bundle(); | 224 Bundle bundle = new Bundle(); |
| 203 | 225 |
| 204 // The restricted utility process can't open the file to read the | 226 // The restricted utility process can't open the file to read the |
| 205 // contents, so we need to obtain a file descriptor to pass over. | 227 // contents, so we need to obtain a file descriptor to pass over. |
| 206 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); | 228 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
| 207 try { | 229 try { |
| 208 try { | 230 try { |
| 209 inputFile = new FileInputStream(file); | 231 inputFile = new FileInputStream(file); |
| 210 FileDescriptor fd = inputFile.getFD(); | 232 FileDescriptor fd = inputFile.getFD(); |
| 211 pfd = ParcelFileDescriptor.dup(fd); | 233 pfd = ParcelFileDescriptor.dup(fd); |
| 212 bundle.putParcelable(DecoderService.KEY_FILE_DESCRIPTOR, pfd); | 234 bundle.putParcelable(DecoderService.KEY_FILE_DESCRIPTOR, pfd); |
| 213 } catch (IOException e) { | 235 } catch (IOException e) { |
| 214 Log.e(TAG, "Unable to obtain FileDescriptor: " + e); | 236 Log.e(TAG, "Unable to obtain FileDescriptor: " + e); |
| 215 closeRequest(filePath, null); | 237 closeRequest(filePath, null, -1); |
| 216 } | 238 } |
| 217 } finally { | 239 } finally { |
| 218 try { | 240 try { |
| 219 if (inputFile != null) inputFile.close(); | 241 if (inputFile != null) inputFile.close(); |
| 220 } catch (IOException e) { | 242 } catch (IOException e) { |
| 221 Log.e(TAG, "Unable to close inputFile: " + e); | 243 Log.e(TAG, "Unable to close inputFile: " + e); |
| 222 } | 244 } |
| 223 StrictMode.setThreadPolicy(oldPolicy); | 245 StrictMode.setThreadPolicy(oldPolicy); |
| 224 } | 246 } |
| 225 | 247 |
| 226 if (pfd == null) return; | 248 if (pfd == null) return; |
| 227 | 249 |
| 228 // Prepare and send the data over. | 250 // Prepare and send the data over. |
| 229 Message payload = Message.obtain(null, DecoderService.MSG_DECODE_IMAGE); | 251 Message payload = Message.obtain(null, DecoderService.MSG_DECODE_IMAGE); |
| 230 payload.replyTo = mMessenger; | 252 payload.replyTo = mMessenger; |
| 231 bundle.putString(DecoderService.KEY_FILE_PATH, filePath); | 253 bundle.putString(DecoderService.KEY_FILE_PATH, filePath); |
| 232 bundle.putInt(DecoderService.KEY_SIZE, size); | 254 bundle.putInt(DecoderService.KEY_SIZE, size); |
| 233 payload.setData(bundle); | 255 payload.setData(bundle); |
| 234 try { | 256 try { |
| 235 mService.send(payload); | 257 mService.send(payload); |
| 236 pfd.close(); | 258 pfd.close(); |
| 237 } catch (RemoteException e) { | 259 } catch (RemoteException e) { |
| 238 Log.e(TAG, "Communications failed (Remote): " + e); | 260 Log.e(TAG, "Communications failed (Remote): " + e); |
| 239 closeRequest(filePath, null); | 261 closeRequest(filePath, null, -1); |
| 240 } catch (IOException e) { | 262 } catch (IOException e) { |
| 241 Log.e(TAG, "Communications failed (IO): " + e); | 263 Log.e(TAG, "Communications failed (IO): " + e); |
| 242 closeRequest(filePath, null); | 264 closeRequest(filePath, null, -1); |
| 243 } | 265 } |
| 244 } | 266 } |
| 245 | 267 |
| 246 /** | 268 /** |
| 247 * Cancels a request to decode an image (if it hasn't already been dispatche
d). | 269 * Cancels a request to decode an image (if it hasn't already been dispatche
d). |
| 248 * @param filePath The path to the image to cancel decoding. | 270 * @param filePath The path to the image to cancel decoding. |
| 249 */ | 271 */ |
| 250 public void cancelDecodeImage(String filePath) { | 272 public void cancelDecodeImage(String filePath) { |
| 251 mRequests.remove(filePath); | 273 mRequests.remove(filePath); |
| 252 } | 274 } |
| (...skipping 24 matching lines...) Expand all Loading... |
| 277 switch (msg.what) { | 299 switch (msg.what) { |
| 278 case DecoderService.MSG_IMAGE_DECODED_REPLY: | 300 case DecoderService.MSG_IMAGE_DECODED_REPLY: |
| 279 Bundle payload = msg.getData(); | 301 Bundle payload = msg.getData(); |
| 280 | 302 |
| 281 // Read the reply back from the service. | 303 // Read the reply back from the service. |
| 282 String filePath = payload.getString(DecoderService.KEY_FILE_
PATH); | 304 String filePath = payload.getString(DecoderService.KEY_FILE_
PATH); |
| 283 Boolean success = payload.getBoolean(DecoderService.KEY_SUCC
ESS); | 305 Boolean success = payload.getBoolean(DecoderService.KEY_SUCC
ESS); |
| 284 Bitmap bitmap = success | 306 Bitmap bitmap = success |
| 285 ? (Bitmap) payload.getParcelable(DecoderService.KEY_
IMAGE_BITMAP) | 307 ? (Bitmap) payload.getParcelable(DecoderService.KEY_
IMAGE_BITMAP) |
| 286 : null; | 308 : null; |
| 287 host.closeRequest(filePath, bitmap); | 309 long decodeTime = payload.getLong(DecoderService.KEY_DECODE_
TIME); |
| 310 host.closeRequest(filePath, bitmap, decodeTime); |
| 288 break; | 311 break; |
| 289 default: | 312 default: |
| 290 super.handleMessage(msg); | 313 super.handleMessage(msg); |
| 291 } | 314 } |
| 292 } | 315 } |
| 293 } | 316 } |
| 294 } | 317 } |
| OLD | NEW |