/* * Copyright (C) 2015 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.messaging.datamodel.media; import android.os.SystemClock; import com.android.messaging.util.Assert; import com.android.messaging.util.LogUtil; import com.google.common.base.Throwables; import java.util.ArrayList; import java.util.concurrent.locks.ReentrantLock; /** * A ref-counted class that holds loaded media resource, be it bitmaps or media bytes. * Subclasses must implement the close() method to release any resources (such as bitmaps) * when it's no longer used. * * Instances of the subclasses are: * 1. Loaded by their corresponding MediaRequest classes. * 2. Maintained by MediaResourceManager in its MediaCache pool. * 3. Used by the UI (such as ContactIconViews) to present the content. * * Note: all synchronized methods in this class (e.g. addRef()) should not attempt to make outgoing * calls that could potentially acquire media cache locks due to the potential deadlock this can * cause. To synchronize read/write access to shared resource, {@link #acquireLock()} and * {@link #releaseLock()} must be used, instead of using synchronized keyword. */ public abstract class RefCountedMediaResource { private final String mKey; private int mRef = 0; private long mLastRefAddTimestamp; // Set DEBUG to true to enable detailed stack trace for each addRef() and release() operation // to find out where each ref change happens. private static final boolean DEBUG = false; private static final String TAG = "bugle_media_ref_history"; private final ArrayList mRefHistory = new ArrayList(); // A lock that guards access to shared members in this class (and all its subclasses). private final ReentrantLock mLock = new ReentrantLock(); public RefCountedMediaResource(final String key) { mKey = key; } public String getKey() { return mKey; } public void addRef() { acquireLock(); try { if (DEBUG) { mRefHistory.add("Added ref current ref = " + mRef); mRefHistory.add(Throwables.getStackTraceAsString(new Exception())); } mRef++; mLastRefAddTimestamp = SystemClock.elapsedRealtime(); } finally { releaseLock(); } } public void release() { acquireLock(); try { if (DEBUG) { mRefHistory.add("Released ref current ref = " + mRef); mRefHistory.add(Throwables.getStackTraceAsString(new Exception())); } mRef--; if (mRef == 0) { close(); } else if (mRef < 0) { if (DEBUG) { LogUtil.i(TAG, "Unwinding ref count history for RefCountedMediaResource " + this); for (final String ref : mRefHistory) { LogUtil.i(TAG, ref); } } Assert.fail("RefCountedMediaResource has unbalanced ref. Refcount=" + mRef); } } finally { releaseLock(); } } public int getRefCount() { acquireLock(); try { return mRef; } finally { releaseLock(); } } public long getLastRefAddTimestamp() { acquireLock(); try { return mLastRefAddTimestamp; } finally { releaseLock(); } } public void assertSingularRefCount() { acquireLock(); try { Assert.equals(1, mRef); } finally { releaseLock(); } } void acquireLock() { mLock.lock(); } void releaseLock() { mLock.unlock(); } void assertLockHeldByCurrentThread() { Assert.isTrue(mLock.isHeldByCurrentThread()); } boolean isEncoded() { return false; } boolean isCacheable() { return true; } MediaRequest getMediaDecodingRequest( final MediaRequest originalRequest) { return null; } MediaRequest getMediaEncodingRequest( final MediaRequest originalRequest) { return null; } public abstract int getMediaSize(); protected abstract void close(); }