1 /*
2  * Copyright (C) 2013 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.camera.session;
18 
19 import android.graphics.Bitmap;
20 import android.location.Location;
21 import android.net.Uri;
22 
23 import com.android.camera.async.MainThread;
24 import com.android.camera.debug.Log;
25 
26 import java.io.File;
27 import java.io.IOException;
28 import java.util.HashMap;
29 import java.util.LinkedList;
30 import java.util.Map;
31 
32 /**
33  * Implementation for the {@link CaptureSessionManager}.
34  * <p>
35  * Basic usage:
36  * <ul>
37  * <li>Create a new capture session.</li>
38  * <li>Pass it around to anywhere where the status of a session needs to be
39  * updated.</li>
40  * <li>If this is a longer operation, use one of the start* methods to indicate
41  * that processing of this session has started. The Camera app right now will
42  * use this to add a new item to the filmstrip and indicate the current
43  * progress.</li>
44  * <li>If the final result is already available and no processing is required,
45  * store the final image using saveAndFinish</li>
46  * <li>For longer operations, update the thumbnail and status message using the
47  * provided methods.</li>
48  * <li>For longer operations, update the thumbnail and status message using the
49  * provided methods.</li>
50  * <li>Once processing is done, the final image can be saved using saveAndFinish
51  * </li>
52  * </ul>
53  * </p>
54  * It's OK to call saveAndFinish either before or after the session has been
55  * started.
56  * <p>
57  * If startSession is called after the session has been finished, it will be
58  * treated as a no-op.
59  * </p>
60  */
61 public class CaptureSessionManagerImpl implements CaptureSessionManager {
62 
63     private final class SessionNotifierImpl implements SessionNotifier {
64         /**
65          * Notifies all task listeners that the task with the given URI has been
66          * queued.
67          */
68         @Override
notifyTaskQueued(final Uri uri)69         public void notifyTaskQueued(final Uri uri) {
70             mMainHandler.execute(new Runnable() {
71                 @Override
72                 public void run() {
73                     synchronized (mTaskListeners) {
74                         for (SessionListener listener : mTaskListeners) {
75                             listener.onSessionQueued(uri);
76                         }
77                     }
78                 }
79             });
80         }
81 
82         /**
83          * Notifies all task listeners that the task with the given URI has been
84          * finished.
85          */
86         @Override
notifyTaskDone(final Uri uri)87         public void notifyTaskDone(final Uri uri) {
88             mMainHandler.execute(new Runnable() {
89                 @Override
90                 public void run() {
91                     synchronized (mTaskListeners) {
92                         for (SessionListener listener : mTaskListeners) {
93                             listener.onSessionDone(uri);
94                         }
95                     }
96                     finalizeSession(uri);
97                 }
98             });
99         }
100 
101         /**
102          * Notifies all task listeners that the task with the given URI has been
103          * failed to process.
104          */
105         @Override
notifyTaskFailed(final Uri uri, final int failureMessageId, final boolean removeFromFilmstrip)106         public void notifyTaskFailed(final Uri uri, final int failureMessageId,
107                 final boolean removeFromFilmstrip) {
108             mMainHandler.execute(new Runnable() {
109                 @Override
110                 public void run() {
111                     synchronized (mTaskListeners) {
112                         for (SessionListener listener : mTaskListeners) {
113                             listener.onSessionFailed(uri, failureMessageId, removeFromFilmstrip);
114                         }
115                     }
116                     finalizeSession(uri);
117                 }
118             });
119         }
120 
121         @Override
notifyTaskCanceled(final Uri uri)122         public void notifyTaskCanceled(final Uri uri) {
123             mMainHandler.execute(new Runnable() {
124                 @Override
125                 public void run() {
126                     synchronized (mTaskListeners) {
127                         for (SessionListener listener : mTaskListeners) {
128                             listener.onSessionCanceled(uri);
129                         }
130                     }
131                     finalizeSession(uri);
132                 }
133             });
134         }
135 
136         /**
137          * Notifies all task listeners that the task with the given URI has
138          * progressed to the given state.
139          */
140         @Override
notifyTaskProgress(final Uri uri, final int progressPercent)141         public void notifyTaskProgress(final Uri uri, final int progressPercent) {
142             mMainHandler.execute(new Runnable() {
143                 @Override
144                 public void run() {
145                     synchronized (mTaskListeners) {
146                         for (SessionListener listener : mTaskListeners) {
147                             listener.onSessionProgress(uri, progressPercent);
148                         }
149                     }
150                 }
151             });
152         }
153 
154         /**
155          * Notifies all task listeners that the task with the given URI has
156          * changed its progress message.
157          */
158         @Override
notifyTaskProgressText(final Uri uri, final int messageId)159         public void notifyTaskProgressText(final Uri uri, final int messageId) {
160             mMainHandler.execute(new Runnable() {
161                 @Override
162                 public void run() {
163                     synchronized (mTaskListeners) {
164                         for (SessionListener listener : mTaskListeners) {
165                             listener.onSessionProgressText(uri, messageId);
166                         }
167                     }
168                 }
169             });
170         }
171 
172         /**
173          * Notifies all task listeners that the media associated with the task
174          * has been updated.
175          */
176         @Override
notifySessionUpdated(final Uri uri)177         public void notifySessionUpdated(final Uri uri) {
178             mMainHandler.execute(new Runnable() {
179                 @Override
180                 public void run() {
181                     synchronized (mTaskListeners) {
182                         for (SessionListener listener : mTaskListeners) {
183                             listener.onSessionUpdated(uri);
184                         }
185                     }
186                 }
187             });
188         }
189 
190         /**
191          * Notifies all task listeners that the task with the given URI has
192          * updated its media.
193          *
194          * @param indicator the bitmap that should be used for the capture
195          *            indicator
196          * @param rotationDegrees the rotation of the updated preview
197          */
198         @Override
notifySessionCaptureIndicatorAvailable(final Bitmap indicator, final int rotationDegrees)199         public void notifySessionCaptureIndicatorAvailable(final Bitmap indicator, final int
200                 rotationDegrees) {
201             mMainHandler.execute(new Runnable() {
202                 @Override
203                 public void run() {
204                     synchronized (mTaskListeners) {
205                         for (SessionListener listener : mTaskListeners) {
206                             listener.onSessionCaptureIndicatorUpdate(indicator, rotationDegrees);
207                         }
208                     }
209                 }
210             });
211         }
212 
213         @Override
notifySessionThumbnailAvailable(final Bitmap thumbnail)214         public void notifySessionThumbnailAvailable(final Bitmap thumbnail) {
215             mMainHandler.execute(new Runnable() {
216                 @Override
217                 public void run() {
218                     synchronized (mTaskListeners) {
219                         for (SessionListener listener : mTaskListeners) {
220                             listener.onSessionThumbnailUpdate(thumbnail);
221                         }
222                     }
223                 }
224             });
225         }
226 
227         @Override
notifySessionPictureDataAvailable( final byte[] pictureData, final int orientation)228         public void notifySessionPictureDataAvailable(
229                 final byte[] pictureData, final int orientation) {
230             mMainHandler.execute(new Runnable() {
231                 @Override
232                 public void run() {
233                     synchronized (mTaskListeners) {
234                         for (SessionListener listener : mTaskListeners) {
235                             listener.onSessionPictureDataUpdate(pictureData, orientation);
236                         }
237                     }
238                 }
239             });
240         }
241     }
242 
243     private static final Log.Tag TAG = new Log.Tag("CaptureSessMgrImpl");
244 
245     /** Sessions in progress, keyed by URI. */
246     private final Map<String, CaptureSession> mSessions;
247     private final SessionNotifier mSessionNotifier;
248     private final CaptureSessionFactory mSessionFactory;
249     private final SessionStorageManager mSessionStorageManager;
250     /** Used to fire events to the session listeners from the main thread. */
251     private final MainThread mMainHandler;
252 
253     /** Failed session messages. Uri -> message ID. */
254     private final HashMap<Uri, Integer> mFailedSessionMessages = new HashMap<>();
255 
256     /** Listeners interested in task update events. */
257     private final LinkedList<SessionListener> mTaskListeners = new LinkedList<SessionListener>();
258 
259     /**
260      * Initializes a new {@link CaptureSessionManager} implementation.
261      *
262      * @param sessionFactory used to create new capture session objects.
263      * @param sessionStorageManager used to tell modules where to store
264      *            temporary session data
265      * @param mainHandler the main handler which listener callback is executed on.
266      */
CaptureSessionManagerImpl( CaptureSessionFactory sessionFactory, SessionStorageManager sessionStorageManager, MainThread mainHandler)267     public CaptureSessionManagerImpl(
268             CaptureSessionFactory sessionFactory,
269             SessionStorageManager sessionStorageManager,
270             MainThread mainHandler) {
271         mSessionFactory = sessionFactory;
272         mSessions = new HashMap<>();
273         mSessionNotifier = new SessionNotifierImpl();
274         mSessionStorageManager = sessionStorageManager;
275         mMainHandler = mainHandler;
276     }
277 
278     @Override
createNewSession(String title, long sessionStartMillis, Location location)279     public CaptureSession createNewSession(String title, long sessionStartMillis, Location location) {
280         return mSessionFactory.createNewSession(this, mSessionNotifier, title, sessionStartMillis,
281                 location);
282     }
283 
284     @Override
putSession(Uri sessionUri, CaptureSession session)285     public void putSession(Uri sessionUri, CaptureSession session) {
286         synchronized (mSessions) {
287             mSessions.put(sessionUri.toString(), session);
288         }
289     }
290 
291     @Override
getSession(Uri sessionUri)292     public CaptureSession getSession(Uri sessionUri) {
293         synchronized (mSessions) {
294             return mSessions.get(sessionUri.toString());
295         }
296     }
297 
298     @Override
removeSession(Uri sessionUri)299     public CaptureSession removeSession(Uri sessionUri) {
300         synchronized (mSessions) {
301             return mSessions.remove(sessionUri.toString());
302         }
303     }
304 
305     @Override
addSessionListener(SessionListener listener)306     public void addSessionListener(SessionListener listener) {
307         synchronized (mTaskListeners) {
308             mTaskListeners.add(listener);
309         }
310     }
311 
312     @Override
removeSessionListener(SessionListener listener)313     public void removeSessionListener(SessionListener listener) {
314         synchronized (mTaskListeners) {
315             mTaskListeners.remove(listener);
316         }
317     }
318 
319     @Override
getSessionDirectory(String subDirectory)320     public File getSessionDirectory(String subDirectory) throws IOException {
321         return mSessionStorageManager.getSessionDirectory(subDirectory);
322     }
323 
324     @Override
hasErrorMessage(Uri uri)325     public boolean hasErrorMessage(Uri uri) {
326         return mFailedSessionMessages.containsKey(uri);
327     }
328 
329     @Override
getErrorMessageId(Uri uri)330     public int getErrorMessageId(Uri uri) {
331         Integer messageId = mFailedSessionMessages.get(uri);
332         if (messageId != null) {
333             return messageId;
334         }
335         return -1;
336     }
337 
338     @Override
removeErrorMessage(Uri uri)339     public void removeErrorMessage(Uri uri) {
340         mFailedSessionMessages.remove(uri);
341     }
342 
343     @Override
putErrorMessage(Uri uri, int failureMessageId)344     public void putErrorMessage(Uri uri, int failureMessageId) {
345         mFailedSessionMessages.put(uri, failureMessageId);
346     }
347 
348     @Override
fillTemporarySession(final SessionListener listener)349     public void fillTemporarySession(final SessionListener listener) {
350         mMainHandler.execute(new Runnable() {
351             @Override
352             public void run() {
353                 synchronized (mSessions) {
354                     for (String sessionUri : mSessions.keySet()) {
355                         CaptureSession session = mSessions.get(sessionUri);
356                         listener.onSessionQueued(session.getUri());
357                         listener.onSessionProgress(session.getUri(), session.getProgress());
358                         listener.onSessionProgressText(session.getUri(),
359                                 session.getProgressMessageId());
360                     }
361                 }
362             }
363         });
364     }
365 
366     /**
367      * When done with a session, remove it from internal map and finalize it.
368      *
369      * @param uri Uri of the session to remove and finalize
370      */
finalizeSession(Uri uri)371     private void finalizeSession(Uri uri) {
372         CaptureSession session;
373         synchronized (mSessions) {
374             session = removeSession(uri);
375         }
376         if (session != null) {
377             session.finalizeSession();
378         }
379     }
380 }
381