1 /* 2 * Copyright 2014 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.hardware.camera2.cts.rs; 18 19 import static android.hardware.camera2.cts.helpers.Preconditions.*; 20 import static junit.framework.Assert.*; 21 22 import android.graphics.ImageFormat; 23 import android.graphics.PixelFormat; 24 import android.util.Size; 25 import android.hardware.camera2.cts.helpers.MaybeNull; 26 import android.hardware.camera2.cts.helpers.UncheckedCloseable; 27 import android.hardware.camera2.cts.rs.Script.ParameterMap; 28 import android.renderscript.Allocation; 29 import android.util.Log; 30 import android.view.Surface; 31 32 import java.lang.reflect.Constructor; 33 import java.lang.reflect.InvocationTargetException; 34 import java.util.ArrayList; 35 import java.util.List; 36 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 37 38 /** 39 * An abstraction to simplify chaining together the execution of multiple RenderScript 40 * {@link android.renderscript.Script scripts} and managing their {@link Allocation allocations}. 41 * 42 * <p>Create a new script graph by using {@link #create}, configure the input with 43 * {@link Builder#configureInput}, then configure one or more scripts with 44 * {@link Builder#configureScript} or {@link Builder#chainScript}. Finally, freeze the graph 45 * with {@link Builder#buildGraph}.</p> 46 * 47 * <p>Once a script graph has been built, all underlying scripts and allocations are instantiated. 48 * Each script may be executed with {@link #execute}. Scripts are executed in the order that they 49 * were configured, with each previous script's output used as the input for the next script. 50 * </p> 51 * 52 * <p>In case the input {@link Allocation} is actually backed by a {@link Surface}, convenience 53 * methods ({@link #advanceInputWaiting} and {@link #advanceInputAndDrop} are provided to 54 * automatically update the {@link Allocation allocation} with the latest buffer available.</p> 55 * 56 * <p>All resources are managed by the {@link ScriptGraph} and {@link #close closing} the graph 57 * will release all underlying resources. See {@link #close} for more details.</p> 58 */ 59 public class ScriptGraph implements UncheckedCloseable { 60 61 private static final String TAG = "ScriptGraph"; 62 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 63 64 private static final int INPUT_SCRIPT_LOCATION = 0; 65 private final int OUTPUT_SCRIPT_LOCATION; // calculated in constructor 66 67 private final AllocationCache mCache = RenderScriptSingleton.getCache(); 68 69 private final Size mSize; 70 private final int mFormat; 71 private final int mUsage; 72 private final List<Script<?>> mScripts; 73 74 private final BlockingInputAllocation mInputBlocker; 75 private final Allocation mOutputAllocation; 76 private boolean mClosed = false; 77 78 /** 79 * Create a new {@link Builder} that will be used to configure the graph's inputs 80 * and scripts (and parameters). 81 * 82 * <p>Once a graph has been fully built, the configuration is immutable.</p> 83 * 84 * @return a {@link Builder} that will be used to configure the graph settings 85 */ create()86 public static Builder create() { 87 return new Builder(); 88 } 89 90 /** 91 * 92 * Check and throw an exception in case the graph was not configured with 93 * {@link Builder#configureInputWithSurface configureInputWithSurface}. 94 * 95 * @throws IllegalArgumentException 96 * if the graph wasn't configured with 97 * {@link Builder#configureInputWithSurface configureInputWithSurface} 98 */ checkInput()99 private void checkInput() { 100 if (!isInputFromSurface()) { 101 throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT"); 102 } 103 } 104 105 /** 106 * Wait until another buffer is produced into the input {@link Surface}, then 107 * update the backing input {@link Allocation} with the latest buffer with 108 * {@link Allocation#ioReceive ioReceive}. 109 * 110 * @throws IllegalArgumentException 111 * if the graph wasn't configured with 112 * {@link Builder#configureInputWithSurface configureInputWithSurface} 113 * @throws TimeoutRuntimeException 114 * if waiting for the buffer times out 115 */ advanceInputWaiting()116 public void advanceInputWaiting() { 117 checkNotClosed(); 118 checkInput(); 119 mInputBlocker.waitForBufferAndReceive(); 120 } 121 122 /** 123 * Wait until another buffer is produced into the input {@link Surface}, then 124 * update the backing input {@link Allocation} with the latest buffer with 125 * {@link Allocation#ioReceive ioReceive}. 126 * 127 * @param timeoutMs wait timeout in milliseconds. 128 * 129 * @throws IllegalArgumentException 130 * if the graph wasn't configured with 131 * {@link Builder#configureInputWithSurface configureInputWithSurface} 132 * @throws TimeoutRuntimeException 133 * if waiting for the buffer times out 134 */ advanceInputWaiting(long timeoutMs)135 public void advanceInputWaiting(long timeoutMs) { 136 checkNotClosed(); 137 checkInput(); 138 mInputBlocker.waitForBufferAndReceive(timeoutMs); 139 } 140 141 /** 142 * Update the backing input {@link Allocation} with the latest buffer with 143 * {@link Allocation#ioReceive ioReceive} repeatedly until no more buffers are pending. 144 * 145 * <p>Does not wait for new buffers to become available if none are currently available 146 * (i.e. {@code false} is returned immediately).</p> 147 * 148 * @return true if any buffers were pending 149 * 150 * @throws IllegalArgumentException 151 * if the graph wasn't configured with 152 * {@link Builder#configureInputWithSurface configureInputWithSurface} 153 * @throws TimeoutRuntimeException 154 * if waiting for the buffer times out 155 */ advanceInputAndDrop()156 public boolean advanceInputAndDrop() { 157 checkNotClosed(); 158 if (!isInputFromSurface()) { 159 throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT"); 160 } 161 162 return mInputBlocker.receiveLatestAvailableBuffers(); 163 } 164 165 /** 166 * Execute each script in the graph, with each next script's input using the 167 * previous script's output. 168 * 169 * <p>Scripts are executed in the same order that they were configured by the {@link Builder}. 170 * </p> 171 * 172 * @throws IllegalStateException if the graph was already {@link #close closed} 173 */ execute()174 public void execute() { 175 checkNotClosed(); 176 177 // TODO: Can we use android.renderscript.ScriptGroup here to make it faster? 178 179 int i = 0; 180 for (Script<?> script : mScripts) { 181 script.execute(); 182 i++; 183 } 184 185 if (VERBOSE) Log.v(TAG, "execute - invoked " + i + " scripts"); 186 } 187 188 /** 189 * Copies the data from the last script's output {@link Allocation} into a byte array. 190 * 191 * <p>The output allocation must be of an 8 bit integer 192 * {@link android.renderscript.Element Element} type.</p> 193 * 194 * @return A byte[] copy. 195 * 196 * @throws IllegalStateException if the graph was already {@link #close closed} 197 */ getOutputData()198 public byte[] getOutputData() { 199 checkNotClosed(); 200 201 Allocation outputAllocation = getOutputAllocation(); 202 203 byte[] destination = new byte[outputAllocation.getBytesSize()]; 204 outputAllocation.copyTo(destination); 205 206 return destination; 207 } 208 209 /** 210 * Copies the data from the first script's input {@link Allocation} into a byte array. 211 * 212 * <p>The input allocation must be of an 8 bit integer 213 * {@link android.renderscript.Element Element} type.</p> 214 * 215 * @return A byte[] copy. 216 * 217 * @throws IllegalStateException if the graph was already {@link #close closed} 218 */ getInputData()219 public byte[] getInputData() { 220 checkNotClosed(); 221 222 Allocation inputAllocation = getInputAllocation(); 223 224 byte[] destination = new byte[inputAllocation.getBytesSize()]; 225 inputAllocation.copyTo(destination); 226 227 return destination; 228 } 229 230 /** 231 * Builds a {@link ScriptGraph} by configuring input size/format/usage, 232 * the script classes in the graph, and the parameters passed to the scripts. 233 * 234 * @see ScriptGraph#create 235 */ 236 public static class Builder { 237 238 private Size mSize; 239 private int mFormat; 240 private int mUsage; 241 242 private final List<ScriptBuilder<? extends Script<?>>> mChainedScriptBuilders = 243 new ArrayList<ScriptBuilder<? extends Script<?>>>(); 244 245 /** 246 * Configure the {@link Allocation} that will be used as the input to the first 247 * script, using the default usage. 248 * 249 * <p>Short hand for calling {@link #configureInput(int, int, int, int)} with a 250 * {@code 0} usage.</p> 251 * 252 * @param width Width in pixels 253 * @param height Height in pixels 254 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 255 * 256 * @return The current builder ({@code this}). Use for chaining method calls. 257 */ configureInput(int width, int height, int format)258 public Builder configureInput(int width, int height, int format) { 259 return configureInput(new Size(width, height), format, /*usage*/0); 260 } 261 262 /** 263 * Configure the {@link Allocation} that will be used as the input to the first 264 * script. 265 * 266 * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p> 267 * 268 * @param width Width in pixels 269 * @param height Height in pixels 270 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 271 * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT} 272 * 273 * @return The current builder ({@code this}). Use for chaining method calls. 274 */ configureInput(int width, int height, int format, int usage)275 public Builder configureInput(int width, int height, int format, int usage) { 276 return configureInput(new Size(width, height), format, usage); 277 } 278 279 /** 280 * Configure the {@link Allocation} that will be used as the input to the first 281 * script, using the default usage. 282 * 283 * <p>Short hand for calling {@link #configureInput(Size, int, int)} with a 284 * {@code 0} usage.</p> 285 * 286 * @param size Size (width, height) 287 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 288 * 289 * @return The current builder ({@code this}). Use for chaining method calls. 290 * 291 * @throws NullPointerException if size was {@code null} 292 */ configureInput(Size size, int format)293 public Builder configureInput(Size size, int format) { 294 return configureInput(size, format, /*usage*/0); 295 } 296 297 /** 298 * Configure the {@link Allocation} that will use a {@link Surface} to produce input into 299 * the first script. 300 * 301 * <p>Short hand for calling {@link #configureInput(Size, int, int)} with the 302 * {@link Allocation#USAGE_IO_INPUT} usage.</p> 303 * 304 * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p> 305 * 306 * @param size Size (width, height) 307 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 308 * 309 * @return The current builder ({@code this}). Use for chaining method calls. 310 * 311 * @throws NullPointerException if size was {@code null} 312 */ configureInputWithSurface(Size size, int format)313 public Builder configureInputWithSurface(Size size, int format) { 314 return configureInput(size, format, Allocation.USAGE_IO_INPUT); 315 } 316 317 /** 318 * Configure the {@link Allocation} that will be used as the input to the first 319 * script. 320 * 321 * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p> 322 * 323 * @param size Size (width, height) 324 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 325 * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT} 326 * 327 * @return The current builder ({@code this}). Use for chaining method calls. 328 * 329 * @throws NullPointerException if size was {@code null} 330 */ configureInput(Size size, int format, int usage)331 public Builder configureInput(Size size, int format, int usage) { 332 checkNotNull("size", size); 333 334 mSize = size; 335 mFormat = format; 336 mUsage = usage | Allocation.USAGE_SCRIPT; 337 338 return this; 339 } 340 341 /** 342 * Build a {@link Script} by setting parameters it might require for execution. 343 * 344 * <p>Refer to the documentation for {@code T} to see if there are any 345 * {@link Script.ScriptParameter parameters} in it. 346 * </p> 347 * 348 * @param <T> Concrete type subclassing the {@link Script} class. 349 */ 350 public class ScriptBuilder<T extends Script<?>> { 351 352 private final Class<T> mScriptClass; 353 ScriptBuilder(Class<T> scriptClass)354 private ScriptBuilder(Class<T> scriptClass) { 355 mScriptClass = scriptClass; 356 } 357 358 private final ParameterMap<T> mParameterMap = new ParameterMap<T>(); 359 360 /** 361 * Set a script parameter to the specified value. 362 * 363 * @param parameter The {@link Script.ScriptParameter parameter key} in {@code T} 364 * @param value A value of type {@code K} that the script expects. 365 * @param <K> The type of the parameter {@code value}. 366 * 367 * @return The current builder ({@code this}). Use to chain method calls. 368 * 369 * @throws NullPointerException if parameter was {@code null} 370 * @throws NullPointerException if value was {@code null} 371 * @throws IllegalStateException if the parameter was already {@link #set} 372 */ set(Script.ScriptParameter<T, K> parameter, K value)373 public <K> ScriptBuilder<T> set(Script.ScriptParameter<T, K> parameter, K value) { 374 checkNotNull("parameter", parameter); 375 checkNotNull("value", value); 376 checkState("Parameter has already been set", !mParameterMap.contains(parameter)); 377 378 mParameterMap.set(parameter, value); 379 380 return this; 381 } 382 getParameterMap()383 ParameterMap<T> getParameterMap() { 384 return mParameterMap; 385 } 386 getScriptClass()387 Class<T> getScriptClass() { 388 return mScriptClass; 389 } 390 391 /** 392 * Build the script and freeze the parameter list to what was {@link #set}. 393 * 394 * @return 395 * The {@link ScriptGraph#Builder} that was used to configure 396 * {@link this} script.</p> 397 */ buildScript()398 public Builder buildScript() { 399 mChainedScriptBuilders.add(this); 400 401 return Builder.this; 402 } 403 } 404 405 /** 406 * Configure the script with no parameters. 407 * 408 * <p>Short hand for invoking {@link #configureScript} immediately followed by 409 * {@link ScriptBuilder#buildScript()}. 410 * 411 * @param scriptClass A concrete class that subclasses {@link Script} 412 * @return The current builder ({@code this}). Use to chain method calls. 413 * 414 * @throws NullPointerException if {@code scriptClass} was {@code null} 415 */ chainScript(Class<T> scriptClass)416 public <T extends Script<?>> Builder chainScript(Class<T> scriptClass) { 417 checkNotNull("scriptClass", scriptClass); 418 419 return (new ScriptBuilder<T>(scriptClass)).buildScript(); 420 } 421 422 /** 423 * Configure the script with parameters. 424 * 425 * <p>Only useful when the {@code scriptClass} has one or more 426 * {@link Script.ScriptParameter script parameters} defined.</p> 427 * 428 * @param scriptClass A concrete class that subclasses {@link Script} 429 * @return A script configuration {@link ScriptBuilder builder}. Use to chain method calls. 430 * 431 * @throws NullPointerException if {@code scriptClass} was {@code null} 432 */ configureScript(Class<T> scriptClass)433 public <T extends Script<?>> ScriptBuilder<T> configureScript(Class<T> scriptClass) { 434 checkNotNull("scriptClass", scriptClass); 435 436 return new ScriptBuilder<T>(scriptClass); 437 } 438 439 /** 440 * Finish configuring the graph and freeze the settings, instantiating all 441 * the {@link Script scripts} and {@link Allocation allocations}. 442 * 443 * @return A constructed {@link ScriptGraph}. 444 */ buildGraph()445 public ScriptGraph buildGraph() { 446 return new ScriptGraph(this); 447 } 448 Builder()449 private Builder() {} 450 } 451 ScriptGraph(Builder builder)452 private ScriptGraph(Builder builder) { 453 mSize = builder.mSize; 454 mFormat = builder.mFormat; 455 mUsage = builder.mUsage; 456 List<Builder.ScriptBuilder<? extends Script<?>>> chainedScriptBuilders = 457 builder.mChainedScriptBuilders; 458 mScripts = new ArrayList<Script<?>>(/*capacity*/chainedScriptBuilders.size()); 459 OUTPUT_SCRIPT_LOCATION = chainedScriptBuilders.size() - 1; 460 461 if (mSize == null) { 462 throw new IllegalArgumentException("Inputs were not configured"); 463 } 464 465 if (chainedScriptBuilders.isEmpty()) { 466 throw new IllegalArgumentException("At least one script should be chained"); 467 } 468 469 /* 470 * The first input is special since it could be USAGE_IO_INPUT. 471 */ 472 AllocationInfo inputInfo = AllocationInfo.newInstance(mSize, mFormat, mUsage); 473 Allocation inputAllocation; 474 475 // Create an Allocation with a Surface if the input to the graph requires it 476 if (isInputFromSurface()) { 477 mInputBlocker = inputInfo.createBlockingInputAllocation(); 478 inputAllocation = mInputBlocker.getAllocation(); 479 } else { 480 mInputBlocker = null; 481 inputAllocation = inputInfo.createAllocation(); 482 } 483 484 if (VERBOSE) Log.v(TAG, "ScriptGraph() - Instantiating all script classes"); 485 486 // Create all scripts. 487 for (Builder.ScriptBuilder<? extends Script<?>> scriptBuilder: chainedScriptBuilders) { 488 489 @SuppressWarnings("unchecked") 490 Class<Script<?>> scriptClass = (Class<Script<?>>) scriptBuilder.getScriptClass(); 491 492 @SuppressWarnings("unchecked") 493 ParameterMap<Script<?>> parameters = (ParameterMap<Script<?>>) 494 scriptBuilder.getParameterMap(); 495 496 Script<?> script = instantiateScript(scriptClass, inputInfo, parameters); 497 mScripts.add(script); 498 499 // The next script's input info is the current script's output info 500 inputInfo = script.getOutputInfo(); 501 } 502 503 if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all inputs"); 504 505 // Create and wire up all inputs. 506 int i = 0; 507 Script<?> inputScript = mScripts.get(INPUT_SCRIPT_LOCATION); 508 do { 509 if (VERBOSE) { 510 Log.v(TAG, "ScriptGraph() - Setting input for script " + inputScript.getName()); 511 } 512 513 inputScript.setInput(inputAllocation); 514 515 i++; 516 517 if (i >= mScripts.size()) { 518 break; 519 } 520 521 // Use the graph input for the first loop iteration 522 inputScript = mScripts.get(i); 523 inputInfo = inputScript.getInputInfo(); 524 inputAllocation = inputInfo.createAllocation(); 525 } while (true); 526 527 if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all outputs"); 528 529 // Create and wire up all outputs. 530 Allocation lastOutput = null; 531 for (i = 0; i < mScripts.size(); ++i) { 532 Script<?> script = mScripts.get(i); 533 Script<?> nextScript = (i + 1 < mScripts.size()) ? mScripts.get(i + 1) : null; 534 535 // Each script's output uses the next script's input. 536 // -- Since the last script has no next script, we own its output allocation. 537 lastOutput = (nextScript != null) ? nextScript.getInput() 538 : script.getOutputInfo().createAllocation(); 539 540 if (VERBOSE) { 541 Log.v(TAG, "ScriptGraph() - Setting output for script " + script.getName()); 542 } 543 544 script.setOutput(lastOutput); 545 } 546 mOutputAllocation = checkNotNull("lastOutput", lastOutput); 547 548 // Done. Safe to execute the graph now. 549 550 if (VERBOSE) Log.v(TAG, "ScriptGraph() - Graph has been built"); 551 } 552 553 /** 554 * Construct the script by instantiating it via reflection. 555 * 556 * <p>The {@link Script scriptClass} should have a {@code Script(AllocationInfo inputInfo)} 557 * constructor if it expects an empty parameter map.</p> 558 * 559 * <p>If it expects a non-empty parameter map, it should have a 560 * {@code Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)} constructor.</p> 561 */ instantiateScript( Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap)562 private static <T extends Script<?>> T instantiateScript( 563 Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap) { 564 565 Constructor<T> ctor; 566 try { 567 // TODO: Would be better if we looked at the script class to see if it expects params 568 if (parameterMap.isEmpty()) { 569 // Script(AllocationInfo inputInfo) 570 ctor = scriptClass.getConstructor(AllocationInfo.class); 571 } else { 572 // Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap) 573 ctor = scriptClass.getConstructor(AllocationInfo.class, ParameterMap.class); 574 } 575 } catch (NoSuchMethodException e) { 576 throw new UnsupportedOperationException( 577 "Script class " + scriptClass + " must have a matching constructor", e); 578 } 579 580 try { 581 if (parameterMap.isEmpty()) { 582 return ctor.newInstance(inputInfo); 583 } else { 584 return ctor.newInstance(inputInfo, parameterMap); 585 } 586 } catch (InstantiationException e) { 587 throw new UnsupportedOperationException(e); 588 } catch (IllegalAccessException e) { 589 throw new UnsupportedOperationException(e); 590 } catch (IllegalArgumentException e) { 591 throw new UnsupportedOperationException(e); 592 } catch (InvocationTargetException e) { 593 throw new UnsupportedOperationException(e); 594 } 595 } 596 isInputFromSurface()597 private boolean isInputFromSurface() { 598 return (mUsage & Allocation.USAGE_IO_INPUT) != 0; 599 } 600 601 /** 602 * Get the input {@link Allocation} that is used by the first script as the input. 603 * 604 * @return An {@link Allocation} (never {@code null}). 605 * 606 * @throws IllegalStateException if the graph was already {@link #close closed} 607 */ getInputAllocation()608 public Allocation getInputAllocation() { 609 checkNotClosed(); 610 611 return mScripts.get(INPUT_SCRIPT_LOCATION).getInput(); 612 } 613 614 /** 615 * Get the output {@link Allocation} that is used by the last script as the output. 616 * 617 * @return An {@link Allocation} (never {@code null}). 618 * 619 * @throws IllegalStateException if the graph was already {@link #close closed} 620 */ getOutputAllocation()621 public Allocation getOutputAllocation() { 622 checkNotClosed(); 623 Allocation output = mScripts.get(OUTPUT_SCRIPT_LOCATION).getOutput(); 624 625 assertEquals("Graph's output should match last script's output", mOutputAllocation, output); 626 627 return output; 628 } 629 630 /** 631 * Get the {@link Surface} that can be used produce buffers into the 632 * {@link #getInputAllocation input allocation}. 633 * 634 * @throws IllegalStateException 635 * if input wasn't configured with {@link Allocation#USAGE_IO_INPUT} {@code usage}. 636 * @throws IllegalStateException 637 * if the graph was already {@link #close closed} 638 * 639 * @return A {@link Surface} (never {@code null}). 640 */ getInputSurface()641 public Surface getInputSurface() { 642 checkNotClosed(); 643 checkState("This graph was not configured with IO_USAGE_INPUT", isInputFromSurface()); 644 645 return getInputAllocation().getSurface(); 646 } 647 checkNotClosed()648 private void checkNotClosed() { 649 checkState("ScriptGraph has been closed", !mClosed); 650 } 651 652 /** 653 * Releases all underlying resources associated with this {@link ScriptGraph}. 654 * 655 * <p>In particular, all underlying {@link Script scripts} and all 656 * {@link Allocation allocations} are also closed.</p> 657 * 658 * <p>All further calls to any other public methods (other than {@link #close}) will throw 659 * an {@link IllegalStateException}.</p> 660 * 661 * <p>This method is idempotent; calling it more than once will 662 * have no further effect.</p> 663 */ 664 @Override close()665 public synchronized void close() { 666 if (mClosed) return; 667 668 for (Script<?> script : mScripts) { 669 script.close(); 670 } 671 mScripts.clear(); 672 673 MaybeNull.close(mInputBlocker); 674 mCache.returnToCache(mOutputAllocation); 675 676 mClosed = true; 677 } 678 679 @Override finalize()680 protected void finalize() throws Throwable { 681 try { 682 close(); 683 } finally { 684 super.finalize(); 685 } 686 } 687 } 688