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.AndroidServices;
27import com.android.camera.util.GservicesHelper;
28
29import java.util.HashMap;
30import java.util.LinkedList;
31
32/**
33 * Default implementation of the {@link MemoryManager}.
34 * <p>
35 * TODO: Add GCam signals.
36 */
37public class MemoryManagerImpl implements MemoryManager, QueueListener, ComponentCallbacks2 {
38    private static final Log.Tag TAG = new Log.Tag("MemoryManagerImpl");
39    /**
40     * Let's signal only 70% of max memory is allowed to be used by native code
41     * to allow a buffer for special captures.
42     */
43    private static final float MAX_MEM_ALLOWED = 0.70f;
44
45    private static final int[] sCriticalStates = new int[] {
46            ComponentCallbacks2.TRIM_MEMORY_COMPLETE,
47            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
48    };
49
50    private final LinkedList<MemoryListener> mListeners = new LinkedList<MemoryListener>();
51
52    /**
53     * The maximum amount of memory allowed to be allocated in native code (in
54     * megabytes)
55     */
56    private final int mMaxAllowedNativeMemory;
57
58    /**
59     * Used to query a breakdown of current memory consumption and memory
60     * thresholds.
61     */
62    private final MemoryQuery mMemoryQuery;
63
64    /**
65     * Use this to create a wired-up memory manager.
66     *
67     * @param context this is used to register for system memory events.
68     * @param mediaSaver this used to check if the saving queue is full.
69     * @return A wired-up memory manager instance.
70     */
71    public static MemoryManagerImpl create(Context context, MediaSaver mediaSaver) {
72        ActivityManager activityManager = AndroidServices.instance().provideActivityManager();
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                .getContentResolver());
154        if (maxAllowedOverrideMb > 0) {
155            Log.d(TAG, "Max native memory overridden: " + maxAllowedOverrideMb);
156            return maxAllowedOverrideMb;
157        }
158
159        ActivityManager activityManager = AndroidServices.instance().provideActivityManager();
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