1 /*
2  * Copyright (C) 2011 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 
18 package android.filterfw.core;
19 
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.filterpacks.base.FrameBranch;
22 import android.filterpacks.base.NullFilter;
23 import android.util.Log;
24 
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.Map.Entry;
30 import java.util.Set;
31 import java.util.Stack;
32 
33 /**
34  * @hide
35  */
36 public class FilterGraph {
37 
38     private HashSet<Filter> mFilters = new HashSet<Filter>();
39     private HashMap<String, Filter> mNameMap = new HashMap<String, Filter>();
40     private HashMap<OutputPort, LinkedList<InputPort>> mPreconnections = new
41             HashMap<OutputPort, LinkedList<InputPort>>();
42 
43     public static final int AUTOBRANCH_OFF      = 0;
44     public static final int AUTOBRANCH_SYNCED   = 1;
45     public static final int AUTOBRANCH_UNSYNCED = 2;
46 
47     public static final int TYPECHECK_OFF       = 0;
48     public static final int TYPECHECK_DYNAMIC   = 1;
49     public static final int TYPECHECK_STRICT    = 2;
50 
51     private boolean mIsReady = false;
52     private int mAutoBranchMode = AUTOBRANCH_OFF;
53     private int mTypeCheckMode = TYPECHECK_STRICT;
54     private boolean mDiscardUnconnectedOutputs = false;
55 
56     private boolean mLogVerbose;
57     private String TAG = "FilterGraph";
58 
FilterGraph()59     public FilterGraph() {
60         mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
61     }
62 
addFilter(Filter filter)63     public boolean addFilter(Filter filter) {
64         if (!containsFilter(filter)) {
65             mFilters.add(filter);
66             mNameMap.put(filter.getName(), filter);
67             return true;
68         }
69         return false;
70     }
71 
containsFilter(Filter filter)72     public boolean containsFilter(Filter filter) {
73         return mFilters.contains(filter);
74     }
75 
76     @UnsupportedAppUsage
getFilter(String name)77     public Filter getFilter(String name) {
78         return mNameMap.get(name);
79     }
80 
connect(Filter source, String outputName, Filter target, String inputName)81     public void connect(Filter source,
82                         String outputName,
83                         Filter target,
84                         String inputName) {
85         if (source == null || target == null) {
86             throw new IllegalArgumentException("Passing null Filter in connect()!");
87         } else if (!containsFilter(source) || !containsFilter(target)) {
88             throw new RuntimeException("Attempting to connect filter not in graph!");
89         }
90 
91         OutputPort outPort = source.getOutputPort(outputName);
92         InputPort inPort = target.getInputPort(inputName);
93         if (outPort == null) {
94             throw new RuntimeException("Unknown output port '" + outputName + "' on Filter " +
95                                        source + "!");
96         } else if (inPort == null) {
97             throw new RuntimeException("Unknown input port '" + inputName + "' on Filter " +
98                                        target + "!");
99         }
100 
101         preconnect(outPort, inPort);
102     }
103 
connect(String sourceName, String outputName, String targetName, String inputName)104     public void connect(String sourceName,
105                         String outputName,
106                         String targetName,
107                         String inputName) {
108         Filter source = getFilter(sourceName);
109         Filter target = getFilter(targetName);
110         if (source == null) {
111             throw new RuntimeException(
112                 "Attempting to connect unknown source filter '" + sourceName + "'!");
113         } else if (target == null) {
114             throw new RuntimeException(
115                 "Attempting to connect unknown target filter '" + targetName + "'!");
116         }
117         connect(source, outputName, target, inputName);
118     }
119 
getFilters()120     public Set<Filter> getFilters() {
121         return mFilters;
122     }
123 
beginProcessing()124     public void beginProcessing() {
125         if (mLogVerbose) Log.v(TAG, "Opening all filter connections...");
126         for (Filter filter : mFilters) {
127             filter.openOutputs();
128         }
129         mIsReady = true;
130     }
131 
flushFrames()132     public void flushFrames() {
133         for (Filter filter : mFilters) {
134             filter.clearOutputs();
135         }
136     }
137 
closeFilters(FilterContext context)138     public void closeFilters(FilterContext context) {
139         if (mLogVerbose) Log.v(TAG, "Closing all filters...");
140         for (Filter filter : mFilters) {
141             filter.performClose(context);
142         }
143         mIsReady = false;
144     }
145 
isReady()146     public boolean isReady() {
147         return mIsReady;
148     }
149 
setAutoBranchMode(int autoBranchMode)150     public void setAutoBranchMode(int autoBranchMode) {
151         mAutoBranchMode = autoBranchMode;
152     }
153 
setDiscardUnconnectedOutputs(boolean discard)154     public void setDiscardUnconnectedOutputs(boolean discard) {
155         mDiscardUnconnectedOutputs = discard;
156     }
157 
setTypeCheckMode(int typeCheckMode)158     public void setTypeCheckMode(int typeCheckMode) {
159         mTypeCheckMode = typeCheckMode;
160     }
161 
162     @UnsupportedAppUsage
tearDown(FilterContext context)163     public void tearDown(FilterContext context) {
164         if (!mFilters.isEmpty()) {
165             flushFrames();
166             for (Filter filter : mFilters) {
167                 filter.performTearDown(context);
168             }
169             mFilters.clear();
170             mNameMap.clear();
171             mIsReady = false;
172         }
173     }
174 
readyForProcessing(Filter filter, Set<Filter> processed)175     private boolean readyForProcessing(Filter filter, Set<Filter> processed) {
176         // Check if this has been already processed
177         if (processed.contains(filter)) {
178             return false;
179         }
180 
181         // Check if all dependencies have been processed
182         for (InputPort port : filter.getInputPorts()) {
183             Filter dependency = port.getSourceFilter();
184             if (dependency != null && !processed.contains(dependency)) {
185                 return false;
186             }
187         }
188         return true;
189     }
190 
runTypeCheck()191     private void runTypeCheck() {
192         Stack<Filter> filterStack = new Stack<Filter>();
193         Set<Filter> processedFilters = new HashSet<Filter>();
194         filterStack.addAll(getSourceFilters());
195 
196         while (!filterStack.empty()) {
197             // Get current filter and mark as processed
198             Filter filter = filterStack.pop();
199             processedFilters.add(filter);
200 
201             // Anchor output formats
202             updateOutputs(filter);
203 
204             // Perform type check
205             if (mLogVerbose) Log.v(TAG, "Running type check on " + filter + "...");
206             runTypeCheckOn(filter);
207 
208             // Push connected filters onto stack
209             for (OutputPort port : filter.getOutputPorts()) {
210                 Filter target = port.getTargetFilter();
211                 if (target != null && readyForProcessing(target, processedFilters)) {
212                     filterStack.push(target);
213                 }
214             }
215         }
216 
217         // Make sure all ports were setup
218         if (processedFilters.size() != getFilters().size()) {
219             throw new RuntimeException("Could not schedule all filters! Is your graph malformed?");
220         }
221     }
222 
updateOutputs(Filter filter)223     private void updateOutputs(Filter filter) {
224         for (OutputPort outputPort : filter.getOutputPorts()) {
225             InputPort inputPort = outputPort.getBasePort();
226             if (inputPort != null) {
227                 FrameFormat inputFormat = inputPort.getSourceFormat();
228                 FrameFormat outputFormat = filter.getOutputFormat(outputPort.getName(),
229                                                                   inputFormat);
230                 if (outputFormat == null) {
231                     throw new RuntimeException("Filter did not return an output format for "
232                         + outputPort + "!");
233                 }
234                 outputPort.setPortFormat(outputFormat);
235             }
236         }
237     }
238 
runTypeCheckOn(Filter filter)239     private void runTypeCheckOn(Filter filter) {
240         for (InputPort inputPort : filter.getInputPorts()) {
241             if (mLogVerbose) Log.v(TAG, "Type checking port " + inputPort);
242             FrameFormat sourceFormat = inputPort.getSourceFormat();
243             FrameFormat targetFormat = inputPort.getPortFormat();
244             if (sourceFormat != null && targetFormat != null) {
245                 if (mLogVerbose) Log.v(TAG, "Checking " + sourceFormat + " against " + targetFormat + ".");
246 
247                 boolean compatible = true;
248                 switch (mTypeCheckMode) {
249                     case TYPECHECK_OFF:
250                         inputPort.setChecksType(false);
251                         break;
252                     case TYPECHECK_DYNAMIC:
253                         compatible = sourceFormat.mayBeCompatibleWith(targetFormat);
254                         inputPort.setChecksType(true);
255                         break;
256                     case TYPECHECK_STRICT:
257                         compatible = sourceFormat.isCompatibleWith(targetFormat);
258                         inputPort.setChecksType(false);
259                         break;
260                 }
261 
262                 if (!compatible) {
263                     throw new RuntimeException("Type mismatch: Filter " + filter + " expects a "
264                         + "format of type " + targetFormat + " but got a format of type "
265                         + sourceFormat + "!");
266                 }
267             }
268         }
269     }
270 
checkConnections()271     private void checkConnections() {
272         // TODO
273     }
274 
discardUnconnectedOutputs()275     private void discardUnconnectedOutputs() {
276         // Connect unconnected ports to Null filters
277         LinkedList<Filter> addedFilters = new LinkedList<Filter>();
278         for (Filter filter : mFilters) {
279             int id = 0;
280             for (OutputPort port : filter.getOutputPorts()) {
281                 if (!port.isConnected()) {
282                     if (mLogVerbose) Log.v(TAG, "Autoconnecting unconnected " + port + " to Null filter.");
283                     NullFilter nullFilter = new NullFilter(filter.getName() + "ToNull" + id);
284                     nullFilter.init();
285                     addedFilters.add(nullFilter);
286                     port.connectTo(nullFilter.getInputPort("frame"));
287                     ++id;
288                 }
289             }
290         }
291         // Add all added filters to this graph
292         for (Filter filter : addedFilters) {
293             addFilter(filter);
294         }
295     }
296 
removeFilter(Filter filter)297     private void removeFilter(Filter filter) {
298         mFilters.remove(filter);
299         mNameMap.remove(filter.getName());
300     }
301 
preconnect(OutputPort outPort, InputPort inPort)302     private void preconnect(OutputPort outPort, InputPort inPort) {
303         LinkedList<InputPort> targets;
304         targets = mPreconnections.get(outPort);
305         if (targets == null) {
306             targets = new LinkedList<InputPort>();
307             mPreconnections.put(outPort, targets);
308         }
309         targets.add(inPort);
310     }
311 
connectPorts()312     private void connectPorts() {
313         int branchId = 1;
314         for (Entry<OutputPort, LinkedList<InputPort>> connection : mPreconnections.entrySet()) {
315             OutputPort outputPort = connection.getKey();
316             LinkedList<InputPort> inputPorts = connection.getValue();
317             if (inputPorts.size() == 1) {
318                 outputPort.connectTo(inputPorts.get(0));
319             } else if (mAutoBranchMode == AUTOBRANCH_OFF) {
320                 throw new RuntimeException("Attempting to connect " + outputPort + " to multiple "
321                                          + "filter ports! Enable auto-branching to allow this.");
322             } else {
323                 if (mLogVerbose) Log.v(TAG, "Creating branch for " + outputPort + "!");
324                 FrameBranch branch = null;
325                 if (mAutoBranchMode == AUTOBRANCH_SYNCED) {
326                     branch = new FrameBranch("branch" + branchId++);
327                 } else {
328                     throw new RuntimeException("TODO: Unsynced branches not implemented yet!");
329                 }
330                 KeyValueMap branchParams = new KeyValueMap();
331                 branch.initWithAssignmentList("outputs", inputPorts.size());
332                 addFilter(branch);
333                 outputPort.connectTo(branch.getInputPort("in"));
334                 Iterator<InputPort> inputPortIter = inputPorts.iterator();
335                 for (OutputPort branchOutPort : ((Filter)branch).getOutputPorts()) {
336                     branchOutPort.connectTo(inputPortIter.next());
337                 }
338             }
339         }
340         mPreconnections.clear();
341     }
342 
getSourceFilters()343     private HashSet<Filter> getSourceFilters() {
344         HashSet<Filter> sourceFilters = new HashSet<Filter>();
345         for (Filter filter : getFilters()) {
346             if (filter.getNumberOfConnectedInputs() == 0) {
347                 if (mLogVerbose) Log.v(TAG, "Found source filter: " + filter);
348                 sourceFilters.add(filter);
349             }
350         }
351         return sourceFilters;
352     }
353 
354     // Core internal methods /////////////////////////////////////////////////////////////////////////
setupFilters()355     void setupFilters() {
356         if (mDiscardUnconnectedOutputs) {
357             discardUnconnectedOutputs();
358         }
359         connectPorts();
360         checkConnections();
361         runTypeCheck();
362     }
363 }
364