summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java
blob: c21f4774b5b3fe3143d9007e1e24a25e12af7ddf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
 * 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<String> mRefHistory = new ArrayList<String>();

    // 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<? extends RefCountedMediaResource> getMediaDecodingRequest(
            final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
        return null;
    }

    MediaRequest<? extends RefCountedMediaResource> getMediaEncodingRequest(
            final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
        return null;
    }

    public abstract int getMediaSize();
    protected abstract void close();
}