• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 android.provider.mediacognitionutils;
18 
19 import static android.provider.MediaCognitionService.ProcessingTypes;
20 
21 import android.annotation.NonNull;
22 import android.database.CursorWindow;
23 import android.os.RemoteException;
24 import android.provider.MediaCognitionProcessingCallback;
25 import android.provider.MediaCognitionProcessingRequest;
26 import android.provider.MediaCognitionProcessingResponse;
27 import android.provider.MediaCognitionService;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * @hide
35  */
36 public final class ProcessMediaCallbackImpl implements MediaCognitionProcessingCallback {
37 
38     private final ICognitionProcessMediaCallbackInternal mBinderCallback;
39 
40     private static final String RESPONSE_CURSOR_NAME = "MediaCognitionResponseCursorWindow";
41 
42     /**
43      * To ensure a callback instance is called only once.
44      */
45     private boolean mCalled;
46 
ProcessMediaCallbackImpl(ICognitionProcessMediaCallbackInternal binderCallback)47     public ProcessMediaCallbackImpl(ICognitionProcessMediaCallbackInternal binderCallback) {
48         mBinderCallback = binderCallback;
49         mCalled = false;
50     }
51 
52     @Override
onSuccess(@onNull List<MediaCognitionProcessingResponse> responses)53     public void onSuccess(@NonNull List<MediaCognitionProcessingResponse> responses) {
54         if (mCalled) {
55             Log.w(MediaCognitionService.TAG, "The callback can only be called once");
56             return;
57         }
58         mCalled = true;
59         if (responses.size() == 0) {
60             Log.w(MediaCognitionService.TAG, "Empty response");
61             return;
62         }
63         try {
64             mBinderCallback.onProcessMediaSuccess(wrapInCursorWindows(responses));
65         } catch (RemoteException e) {
66             Log.w(MediaCognitionService.TAG,
67                     "Unable to send callback of Process Media result", e);
68         }
69 
70     }
71 
72     @Override
onFailure(@onNull String message)73     public void onFailure(@NonNull String message) {
74         if (mCalled) {
75             Log.w(MediaCognitionService.TAG, "The callback can only be called once");
76             return;
77         }
78         mCalled = true;
79         try {
80             mBinderCallback.onProcessMediaFailure(message);
81         } catch (RemoteException e) {
82             Log.w(MediaCognitionService.TAG,
83                     "Unable to send callback of Process Media failure", e);
84         }
85     }
86 
87 
88     /**
89      * This runs in the app's process which has implemented {@link MediaCognitionService}
90      * Before passing the response to MediaProvider, the responses should be wrapped
91      * using {@link CursorWindow} to avoid risk of parcelable limit of an ipc.
92      *
93      */
wrapInCursorWindows(List<MediaCognitionProcessingResponse> responses)94     private CursorWindow[] wrapInCursorWindows(List<MediaCognitionProcessingResponse> responses) {
95         ArrayList<CursorWindow> windows = new ArrayList<CursorWindow>();
96         CursorWindow window = new CursorWindow(RESPONSE_CURSOR_NAME);
97         // number of columns =  number of processing available + Id of the media
98         final int numColumns = ProcessingTypes.class.getDeclaredFields().length + 1;
99         window.setNumColumns(numColumns);
100         windows.add(window);
101         /*
102         In the following loop, we iterate through responses and add them to current CursorWindow.
103         If the window is full then addition of a row to that window will fail.
104         At this point we create a new window.
105          */
106         try {
107             boolean retryingRowAddition = false;
108             for (int row = 0; row < responses.size(); row++) {
109                 // Allocate a new row for each entry.
110                 if (!window.allocRow()) {
111                     // This window is full. Allocate a new CursorWindow.
112                     Log.d(MediaCognitionService.TAG, "Allocating additional cursor"
113                             + " window for large data set (row " + row + ")");
114                     window = new CursorWindow(RESPONSE_CURSOR_NAME);
115                     window.setStartPosition(row);
116                     window.setNumColumns(numColumns);
117                     windows.add(window);
118                     if (!window.allocRow()) {
119                         // If we couldn't allocate a row even after creating a new CursorWindow, we
120                         // can't proceed.
121                         Log.e(MediaCognitionService.TAG, "Unable to allocate row"
122                                 + " to hold data.");
123                         windows.remove(window);
124                         return windows.toArray(new CursorWindow[windows.size()]);
125                     }
126                 }
127 
128                 // current window is ready to be populated with data
129                 boolean dataAdded = true;
130 
131                 // Adding ID of the media
132                 final MediaCognitionProcessingRequest request = responses.get(row).getRequest();
133                 if (request == null || request.getUri() == null
134                         || request.getUri().getLastPathSegment() == null) {
135                     Log.w(MediaCognitionService.TAG, "Correct request not"
136                             + " set in the response");
137                     window.freeLastRow();
138                     retryingRowAddition = false;
139                     continue;
140                 }
141                 dataAdded &= window.putString(request.getUri().getLastPathSegment(),
142                         row, 0);
143                 dataAdded &= addImageOcrLatin(window, responses.get(row), request, row);
144                 dataAdded &= addImageLabels(window, responses.get(row), request, row);
145 
146                 if (!dataAdded) {
147                     // if data addition failed, retry with a new window
148                     if (retryingRowAddition) {
149                         // if already retrying and failed again, skip this response
150                         Log.w(MediaCognitionService.TAG, "Could not add value to cursorWindow"
151                                 + " The size may be larger than limit ");
152                         // prepare for next item
153                         window.clear();
154                         window.setStartPosition(row + 1);
155                         window.setNumColumns(numColumns);
156                         retryingRowAddition = false;
157                         continue;
158                     }
159                     Log.d(MediaCognitionService.TAG, "Couldn't populate window "
160                             + "data for row " + row + " - allocating new window.");
161                     window.freeLastRow();
162                     window = new CursorWindow(RESPONSE_CURSOR_NAME);
163                     window.setStartPosition(row);
164                     window.setNumColumns(numColumns);
165                     windows.add(window);
166                     row--;
167                     retryingRowAddition = true;
168                 } else {
169                     retryingRowAddition = false;
170                 }
171             }
172         } catch (RuntimeException re) {
173             // Something went wrong while filling the window. Close all windows, and re-throw.
174             for (int i = 0, count = windows.size(); i < count; i++) {
175                 windows.get(i).close();
176             }
177             throw re;
178         }
179         return windows.toArray(new CursorWindow[windows.size()]);
180     }
181 
addImageOcrLatin(CursorWindow window, MediaCognitionProcessingResponse response, MediaCognitionProcessingRequest request, int row)182     private boolean addImageOcrLatin(CursorWindow window, MediaCognitionProcessingResponse response,
183             MediaCognitionProcessingRequest request, int row) {
184         final int column = Integer.numberOfTrailingZeros(ProcessingTypes.IMAGE_OCR_LATIN) + 1;
185         // check if ImageOcrLatin request was set
186         if (request.checkProcessingRequired(ProcessingTypes.IMAGE_OCR_LATIN)
187                 && response.getImageOcrLatin() != null) {
188             return window.putString(response.getImageOcrLatin(), row, column);
189         }
190         return window.putNull(row, column);
191     }
192 
addImageLabels(CursorWindow window, MediaCognitionProcessingResponse response, MediaCognitionProcessingRequest request, int row)193     private boolean addImageLabels(CursorWindow window, MediaCognitionProcessingResponse response,
194             MediaCognitionProcessingRequest request, int row) {
195         final int column = Integer.numberOfTrailingZeros(ProcessingTypes.IMAGE_LABEL) + 1;
196         if (request.checkProcessingRequired(ProcessingTypes.IMAGE_LABEL)
197                 && response.getImageLabels() != null) {
198             return window.putString(String.join("|", response.getImageLabels()),
199                     row, column);
200         }
201         return window.putNull(row, column);
202     }
203 }
204 
205