1 /*
2  * Copyright (C) 2015 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 package com.android.messaging.datamodel.media;
17 
18 import android.os.SystemClock;
19 
20 import com.android.messaging.util.Assert;
21 import com.android.messaging.util.LogUtil;
22 import com.google.common.base.Throwables;
23 
24 import java.util.ArrayList;
25 import java.util.concurrent.locks.ReentrantLock;
26 
27 /**
28  * A ref-counted class that holds loaded media resource, be it bitmaps or media bytes.
29  * Subclasses must implement the close() method to release any resources (such as bitmaps)
30  * when it's no longer used.
31  *
32  * Instances of the subclasses are:
33  * 1. Loaded by their corresponding MediaRequest classes.
34  * 2. Maintained by MediaResourceManager in its MediaCache pool.
35  * 3. Used by the UI (such as ContactIconViews) to present the content.
36  *
37  * Note: all synchronized methods in this class (e.g. addRef()) should not attempt to make outgoing
38  * calls that could potentially acquire media cache locks due to the potential deadlock this can
39  * cause. To synchronize read/write access to shared resource, {@link #acquireLock()} and
40  * {@link #releaseLock()} must be used, instead of using synchronized keyword.
41  */
42 public abstract class RefCountedMediaResource {
43     private final String mKey;
44     private int mRef = 0;
45     private long mLastRefAddTimestamp;
46 
47     // Set DEBUG to true to enable detailed stack trace for each addRef() and release() operation
48     // to find out where each ref change happens.
49     private static final boolean DEBUG = false;
50     private static final String TAG = "bugle_media_ref_history";
51     private final ArrayList<String> mRefHistory = new ArrayList<String>();
52 
53     // A lock that guards access to shared members in this class (and all its subclasses).
54     private final ReentrantLock mLock = new ReentrantLock();
55 
RefCountedMediaResource(final String key)56     public RefCountedMediaResource(final String key) {
57         mKey = key;
58     }
59 
getKey()60     public String getKey() {
61         return mKey;
62     }
63 
addRef()64     public void addRef() {
65         acquireLock();
66         try {
67             if (DEBUG) {
68                 mRefHistory.add("Added ref current ref = " + mRef);
69                 mRefHistory.add(Throwables.getStackTraceAsString(new Exception()));
70             }
71 
72             mRef++;
73             mLastRefAddTimestamp = SystemClock.elapsedRealtime();
74         } finally {
75             releaseLock();
76         }
77     }
78 
release()79     public void release() {
80         acquireLock();
81         try {
82             if (DEBUG) {
83                 mRefHistory.add("Released ref current ref = " + mRef);
84                 mRefHistory.add(Throwables.getStackTraceAsString(new Exception()));
85             }
86 
87             mRef--;
88             if (mRef == 0) {
89                 close();
90             } else if (mRef < 0) {
91                 if (DEBUG) {
92                     LogUtil.i(TAG, "Unwinding ref count history for RefCountedMediaResource "
93                             + this);
94                     for (final String ref : mRefHistory) {
95                         LogUtil.i(TAG, ref);
96                     }
97                 }
98                 Assert.fail("RefCountedMediaResource has unbalanced ref. Refcount=" + mRef);
99             }
100         } finally {
101             releaseLock();
102         }
103     }
104 
getRefCount()105     public int getRefCount() {
106         acquireLock();
107         try {
108             return mRef;
109         } finally {
110             releaseLock();
111         }
112     }
113 
getLastRefAddTimestamp()114     public long getLastRefAddTimestamp() {
115         acquireLock();
116         try {
117             return mLastRefAddTimestamp;
118         } finally {
119             releaseLock();
120         }
121     }
122 
assertSingularRefCount()123     public void assertSingularRefCount() {
124         acquireLock();
125         try {
126             Assert.equals(1, mRef);
127         } finally {
128             releaseLock();
129         }
130     }
131 
acquireLock()132     void acquireLock() {
133         mLock.lock();
134     }
135 
releaseLock()136     void releaseLock() {
137         mLock.unlock();
138     }
139 
assertLockHeldByCurrentThread()140     void assertLockHeldByCurrentThread() {
141         Assert.isTrue(mLock.isHeldByCurrentThread());
142     }
143 
isEncoded()144     boolean isEncoded() {
145         return false;
146     }
147 
isCacheable()148     boolean isCacheable() {
149         return true;
150     }
151 
getMediaDecodingRequest( final MediaRequest<? extends RefCountedMediaResource> originalRequest)152     MediaRequest<? extends RefCountedMediaResource> getMediaDecodingRequest(
153             final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
154         return null;
155     }
156 
getMediaEncodingRequest( final MediaRequest<? extends RefCountedMediaResource> originalRequest)157     MediaRequest<? extends RefCountedMediaResource> getMediaEncodingRequest(
158             final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
159         return null;
160     }
161 
getMediaSize()162     public abstract int getMediaSize();
close()163     protected abstract void close();
164 }