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.app.Service; | 7 import android.app.Service; |
8 import android.content.Intent; | 8 import android.content.Intent; |
9 import android.graphics.Bitmap; | 9 import android.graphics.Bitmap; |
10 import android.os.Bundle; | 10 import android.os.Bundle; |
11 import android.os.Handler; | |
12 import android.os.IBinder; | 11 import android.os.IBinder; |
13 import android.os.Message; | |
14 import android.os.Messenger; | |
15 import android.os.ParcelFileDescriptor; | 12 import android.os.ParcelFileDescriptor; |
16 import android.os.RemoteException; | 13 import android.os.RemoteException; |
17 import android.os.SystemClock; | 14 import android.os.SystemClock; |
18 | 15 |
19 import org.chromium.base.Log; | 16 import org.chromium.base.Log; |
20 | 17 |
21 import java.io.FileDescriptor; | 18 import java.io.FileDescriptor; |
22 import java.io.IOException; | 19 import java.io.IOException; |
23 | 20 |
24 /** | 21 /** |
25 * A service to accept requests to take image file contents and decode them. | 22 * A service to accept requests to take image file contents and decode them. |
26 */ | 23 */ |
27 public class DecoderService extends Service { | 24 public class DecoderService extends Service { |
28 // Message ids for communicating with the client. | |
29 | |
30 // A message sent by the client to decode an image. | |
31 static final int MSG_DECODE_IMAGE = 1; | |
32 // A message sent by the server to notify the client of the results of the d
ecoding. | |
33 static final int MSG_IMAGE_DECODED_REPLY = 2; | |
34 | |
35 // The keys for the bundle when passing data to and from this service. | 25 // The keys for the bundle when passing data to and from this service. |
36 static final String KEY_FILE_DESCRIPTOR = "file_descriptor"; | 26 static final String KEY_FILE_DESCRIPTOR = "file_descriptor"; |
37 static final String KEY_FILE_PATH = "file_path"; | 27 static final String KEY_FILE_PATH = "file_path"; |
38 static final String KEY_IMAGE_BITMAP = "image_bitmap"; | 28 static final String KEY_IMAGE_BITMAP = "image_bitmap"; |
39 static final String KEY_IMAGE_BYTE_COUNT = "image_byte_count"; | |
40 static final String KEY_IMAGE_DESCRIPTOR = "image_descriptor"; | |
41 static final String KEY_SIZE = "size"; | 29 static final String KEY_SIZE = "size"; |
42 static final String KEY_SUCCESS = "success"; | 30 static final String KEY_SUCCESS = "success"; |
43 static final String KEY_DECODE_TIME = "decode_time"; | 31 static final String KEY_DECODE_TIME = "decode_time"; |
44 | 32 |
45 // A tag for logging error messages. | 33 // A tag for logging error messages. |
46 private static final String TAG = "ImageDecoder"; | 34 private static final String TAG = "ImageDecoder"; |
47 | 35 |
48 /** | 36 @Override |
49 * Handler for incoming messages from clients. | 37 public void onCreate() { |
50 */ | 38 super.onCreate(); |
51 static class IncomingHandler extends Handler { | 39 } |
52 @Override | |
53 public void handleMessage(Message msg) { | |
54 switch (msg.what) { | |
55 case MSG_DECODE_IMAGE: | |
56 Bundle bundle = null; | |
57 Messenger client = null; | |
58 String filePath = ""; | |
59 int size = 0; | |
60 try { | |
61 Bundle payload = msg.getData(); | |
62 client = msg.replyTo; | |
63 | 40 |
64 filePath = payload.getString(KEY_FILE_PATH); | 41 @Override |
65 ParcelFileDescriptor pfd = payload.getParcelable(KEY_FIL
E_DESCRIPTOR); | 42 public IBinder onBind(Intent intent) { |
66 size = payload.getInt(KEY_SIZE); | 43 return mBinder; |
| 44 } |
67 | 45 |
68 // Setup a minimum viable response to parent process. Wi
ll be fleshed out | 46 private final IDecoderService.Stub mBinder = new IDecoderService.Stub() { |
69 // further below. | 47 public void decodeImage(Bundle payload, IDecoderServiceCallback callback
) { |
70 bundle = new Bundle(); | 48 Bundle bundle = null; |
71 bundle.putString(KEY_FILE_PATH, filePath); | 49 String filePath = ""; |
72 bundle.putBoolean(KEY_SUCCESS, false); | 50 int size = 0; |
| 51 try { |
| 52 filePath = payload.getString(KEY_FILE_PATH); |
| 53 ParcelFileDescriptor pfd = payload.getParcelable(KEY_FILE_DESCRI
PTOR); |
| 54 size = payload.getInt(KEY_SIZE); |
73 | 55 |
74 FileDescriptor fd = pfd.getFileDescriptor(); | 56 // Setup a minimum viable response to parent process. Will be fl
eshed out |
| 57 // further below. |
| 58 bundle = new Bundle(); |
| 59 bundle.putString(KEY_FILE_PATH, filePath); |
| 60 bundle.putBoolean(KEY_SUCCESS, false); |
75 | 61 |
76 long begin = SystemClock.elapsedRealtime(); | 62 FileDescriptor fd = pfd.getFileDescriptor(); |
77 Bitmap bitmap = BitmapUtils.decodeBitmapFromFileDescript
or(fd, size); | |
78 long decodeTime = SystemClock.elapsedRealtime() - begin; | |
79 | 63 |
80 try { | 64 long begin = SystemClock.elapsedRealtime(); |
81 pfd.close(); | 65 Bitmap bitmap = BitmapUtils.decodeBitmapFromFileDescriptor(fd, s
ize); |
82 } catch (IOException e) { | 66 long decodeTime = SystemClock.elapsedRealtime() - begin; |
83 Log.e(TAG, "Closing failed " + filePath + " (size: "
+ size + ") " + e); | |
84 } | |
85 | 67 |
86 if (bitmap == null) { | 68 try { |
87 Log.e(TAG, "Decode failed " + filePath + " (size: "
+ size + ")"); | 69 pfd.close(); |
88 sendReply(client, bundle); // Sends SUCCESS == false
; | 70 } catch (IOException e) { |
89 return; | 71 Log.e(TAG, "Closing failed " + filePath + " (size: " + size
+ ") " + e); |
90 } | 72 } |
91 | 73 |
92 // The most widely supported, easiest, and reasonably ef
ficient method is to | 74 if (bitmap == null) { |
93 // decode to an immutable bitmap and just return the bit
map over binder. It | 75 Log.e(TAG, "Decode failed " + filePath + " (size: " + size +
")"); |
94 // will internally memcpy itself to ashmem and then just
send over the file | 76 sendReply(callback, bundle); // Sends SUCCESS == false; |
95 // descriptor. In the receiving process it will just lea
ve the bitmap on | 77 return; |
96 // ashmem since it's immutable and carry on. | 78 } |
97 bundle.putParcelable(KEY_IMAGE_BITMAP, bitmap); | |
98 bundle.putBoolean(KEY_SUCCESS, true); | |
99 bundle.putLong(KEY_DECODE_TIME, decodeTime); | |
100 sendReply(client, bundle); | |
101 bitmap.recycle(); | |
102 } catch (Exception e) { | |
103 // This service has no UI and maintains no state so if i
t crashes on | |
104 // decoding a photo, it is better UX to eat the exceptio
n instead of showing | |
105 // a crash dialog and discarding other requests that hav
e already been sent. | |
106 Log.e(TAG, | |
107 "Unexpected error during decoding " + filePath +
" (size: " + size | |
108 + ") " + e); | |
109 | 79 |
110 if (bundle != null && client != null) sendReply(client,
bundle); | 80 // The most widely supported, easiest, and reasonably efficient
method is to |
111 } | 81 // decode to an immutable bitmap and just return the bitmap over
binder. It |
112 break; | 82 // will internally memcpy itself to ashmem and then just send ov
er the file |
113 default: | 83 // descriptor. In the receiving process it will just leave the b
itmap on |
114 super.handleMessage(msg); | 84 // ashmem since it's immutable and carry on. |
| 85 bundle.putParcelable(KEY_IMAGE_BITMAP, bitmap); |
| 86 bundle.putBoolean(KEY_SUCCESS, true); |
| 87 bundle.putLong(KEY_DECODE_TIME, decodeTime); |
| 88 sendReply(callback, bundle); |
| 89 bitmap.recycle(); |
| 90 } catch (Exception e) { |
| 91 // This service has no UI and maintains no state so if it crashe
s on |
| 92 // decoding a photo, it is better UX to eat the exception instea
d of showing |
| 93 // a crash dialog and discarding other requests that have alread
y been sent. |
| 94 Log.e(TAG, |
| 95 "Unexpected error during decoding " + filePath + " (size
: " + size + ") " |
| 96 + e); |
| 97 |
| 98 if (bundle != null) sendReply(callback, bundle); |
115 } | 99 } |
116 } | 100 } |
117 | 101 |
118 private void sendReply(Messenger client, Bundle bundle) { | 102 private void sendReply(IDecoderServiceCallback callback, Bundle bundle)
{ |
119 Message reply = Message.obtain(null, MSG_IMAGE_DECODED_REPLY); | |
120 reply.setData(bundle); | |
121 try { | 103 try { |
122 client.send(reply); | 104 callback.onDecodeImageDone(bundle); |
123 } catch (RemoteException remoteException) { | 105 } catch (RemoteException remoteException) { |
124 Log.e(TAG, "Remote error while replying: " + remoteException); | 106 Log.e(TAG, "Remote error while replying: " + remoteException); |
125 } | 107 } |
126 } | 108 } |
127 } | 109 }; |
128 | |
129 /** | |
130 * The target we publish for clients to send messages to IncomingHandler. | |
131 */ | |
132 final Messenger mMessenger = new Messenger(new IncomingHandler()); | |
133 | |
134 /** | |
135 * When binding to the service, we return an interface to our messenger | |
136 * for sending messages to the service. | |
137 */ | |
138 @Override | |
139 public IBinder onBind(Intent intent) { | |
140 return mMessenger.getBinder(); | |
141 } | |
142 } | 110 } |
OLD | NEW |