/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.net; import android.os.Handler; import android.os.HandlerThread; import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; /** * This class provides APIs to do a delayed data write to a given {@link OutputStream}. */ public class DelayedDiskWrite { private static final String TAG = "DelayedDiskWrite"; private final Dependencies mDeps; private HandlerThread mDiskWriteHandlerThread; private Handler mDiskWriteHandler; /* Tracks multiple writes on the same thread */ private int mWriteSequence = 0; public DelayedDiskWrite() { this(new Dependencies()); } @VisibleForTesting DelayedDiskWrite(Dependencies deps) { mDeps = deps; } /** * Dependencies class of DelayedDiskWrite, used for injection in tests. */ @VisibleForTesting static class Dependencies { /** * Create a HandlerThread to use in DelayedDiskWrite. */ public HandlerThread makeHandlerThread() { return new HandlerThread("DelayedDiskWriteThread"); } /** * Quit the HandlerThread looper. */ public void quitHandlerThread(HandlerThread handlerThread) { handlerThread.getLooper().quit(); } } /** * Used to do a delayed data write to a given {@link OutputStream}. */ public interface Writer { /** * write data to a given {@link OutputStream}. */ void onWriteCalled(DataOutputStream out) throws IOException; } /** * Do a delayed data write to a given output stream opened from filePath. */ public void write(final String filePath, final Writer w) { write(filePath, w, true); } /** * Do a delayed data write to a given output stream opened from filePath. */ public void write(final String filePath, final Writer w, final boolean open) { if (TextUtils.isEmpty(filePath)) { throw new IllegalArgumentException("empty file path"); } /* Do a delayed write to disk on a separate handler thread */ synchronized (this) { if (++mWriteSequence == 1) { mDiskWriteHandlerThread = mDeps.makeHandlerThread(); mDiskWriteHandlerThread.start(); mDiskWriteHandler = new Handler(mDiskWriteHandlerThread.getLooper()); } } mDiskWriteHandler.post(new Runnable() { @Override public void run() { doWrite(filePath, w, open); } }); } private void doWrite(String filePath, Writer w, boolean open) { DataOutputStream out = null; try { if (open) { out = new DataOutputStream(new BufferedOutputStream( new FileOutputStream(filePath))); } w.onWriteCalled(out); } catch (IOException e) { loge("Error writing data file " + filePath); } finally { if (out != null) { try { out.close(); } catch (Exception e) { } } // Quit if no more writes sent synchronized (this) { if (--mWriteSequence == 0) { mDeps.quitHandlerThread(mDiskWriteHandlerThread); mDiskWriteHandlerThread = null; mDiskWriteHandler = null; } } } } private void loge(String s) { Log.e(TAG, s); } }