1/*
2 * Copyright (C) 2013 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
17package com.android.camera.app;
18
19import android.app.ActivityManager;
20import android.content.ComponentCallbacks2;
21import android.content.Context;
22import android.content.res.Configuration;
23
24import com.android.camera.app.MediaSaver.QueueListener;
25import com.android.camera.debug.Log;
26import com.android.camera.util.GservicesHelper;
27
28import java.util.HashMap;
29import java.util.LinkedList;
30
31/**
32 * Default implementation of the {@link MemoryManager}.
33 * <p>
34 * TODO: Add GCam signals.
35 */
36public class MemoryManagerImpl implements MemoryManager, QueueListener, ComponentCallbacks2 {
37    private static final Log.Tag TAG = new Log.Tag("MemoryManagerImpl");
38    /**
39     * Let's signal only 70% of max memory is allowed to be used by native code
40     * to allow a buffer for special captures.
41     */
42    private static final float MAX_MEM_ALLOWED = 0.70f;
43
44    private static final int[] sCriticalStates = new int[] {
45            ComponentCallbacks2.TRIM_MEMORY_COMPLETE,
46            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
47    };
48
49    private final LinkedList<MemoryListener> mListeners = new LinkedList<MemoryListener>();
50
51    /**
52     * The maximum amount of memory allowed to be allocated in native code (in
53     * megabytes)
54     */
55    private final int mMaxAllowedNativeMemory;
56
57    /**
58     * Used to query a breakdown of current memory consumption and memory
59     * thresholds.
60     */
61    private final MemoryQuery mMemoryQuery;
62
63    /**
64     * Use this to create a wired-up memory manager.
65     *
66     * @param context this is used to register for system memory events.
67     * @param mediaSaver this used to check if the saving queue is full.
68     * @return A wired-up memory manager instance.
69     */
70    public static MemoryManagerImpl create(Context context, MediaSaver mediaSaver) {
71        ActivityManager activityManager =
72                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
73        int maxAllowedNativeMemory = getMaxAllowedNativeMemory(context);
74        MemoryQuery mMemoryQuery = new MemoryQuery(activityManager);
75        MemoryManagerImpl memoryManager = new MemoryManagerImpl(maxAllowedNativeMemory,
76                mMemoryQuery);
77        context.registerComponentCallbacks(memoryManager);
78        mediaSaver.setQueueListener(memoryManager);
79        return memoryManager;
80    }
81
82    /**
83     * Use {@link #create(Context, MediaSaver)} to make sure it's wired up
84     * correctly.
85     */
86    private MemoryManagerImpl(int maxAllowedNativeMemory, MemoryQuery memoryQuery) {
87        mMaxAllowedNativeMemory = maxAllowedNativeMemory;
88        mMemoryQuery = memoryQuery;
89        Log.d(TAG, "Max native memory: " + mMaxAllowedNativeMemory + " MB");
90
91    }
92
93    @Override
94    public void addListener(MemoryListener listener) {
95        synchronized (mListeners) {
96            if (!mListeners.contains(listener)) {
97                mListeners.add(listener);
98            } else {
99                Log.w(TAG, "Listener already added.");
100            }
101        }
102    }
103
104    @Override
105    public void removeListener(MemoryListener listener) {
106        synchronized (mListeners) {
107            if (mListeners.contains(listener)) {
108                mListeners.remove(listener);
109            } else {
110                Log.w(TAG, "Cannot remove listener that was never added.");
111            }
112        }
113    }
114
115    @Override
116    public void onConfigurationChanged(Configuration newConfig) {
117    }
118
119    @Override
120    public void onLowMemory() {
121        notifyLowMemory();
122    }
123
124    @Override
125    public void onTrimMemory(int level) {
126        for (int i = 0; i < sCriticalStates.length; ++i) {
127            if (level == sCriticalStates[i]) {
128                notifyLowMemory();
129                return;
130            }
131        }
132    }
133
134    @Override
135    public void onQueueStatus(boolean full) {
136        notifyCaptureStateUpdate(full ? STATE_LOW_MEMORY : STATE_OK);
137    }
138
139    @Override
140    public int getMaxAllowedNativeMemoryAllocation() {
141        return mMaxAllowedNativeMemory;
142    }
143
144    @Override
145    public HashMap queryMemory() {
146        return mMemoryQuery.queryMemory();
147    }
148
149    /** Helper to determine max allowed native memory allocation (in megabytes). */
150    private static int getMaxAllowedNativeMemory(Context context) {
151        // First check whether we have a system override.
152        int maxAllowedOverrideMb = GservicesHelper.getMaxAllowedNativeMemoryMb(context);
153        if (maxAllowedOverrideMb > 0) {
154            Log.d(TAG, "Max native memory overridden: " + maxAllowedOverrideMb);
155            return maxAllowedOverrideMb;
156        }
157
158        ActivityManager activityManager = (ActivityManager) context
159                .getSystemService(Context.ACTIVITY_SERVICE);
160
161        // Use the max of the regular memory class and the large memory class.
162        // This is defined as the maximum memory allowed to be used by the
163        // Dalvik heap, but it's safe to assume the app can use the same amount
164        // once more in native code.
165        return (int) (Math.max(activityManager.getMemoryClass(),
166                activityManager.getLargeMemoryClass()) * MAX_MEM_ALLOWED);
167    }
168
169    /** Notify our listener that memory is running low. */
170    private void notifyLowMemory() {
171        synchronized (mListeners) {
172            for (MemoryListener listener : mListeners) {
173                listener.onLowMemory();
174            }
175        }
176    }
177
178    private void notifyCaptureStateUpdate(int captureState) {
179        synchronized (mListeners) {
180            for (MemoryListener listener : mListeners) {
181                listener.onMemoryStateChanged(captureState);
182            }
183        }
184    }
185}
186