RecordingActivityMonitor.java revision d3c71f075b139024e2bea39bbd75e3b976bfb7cb
1/*
2 * Copyright (C) 2016 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.server.audio;
18
19import android.media.AudioManager;
20import android.media.AudioRecordConfiguration;
21import android.media.AudioSystem;
22import android.media.IRecordingConfigDispatcher;
23import android.os.IBinder;
24import android.os.RemoteException;
25import android.util.Log;
26
27import java.util.ArrayList;
28import java.util.HashMap;
29import java.util.Iterator;
30
31/**
32 * Class to receive and dispatch updates from AudioSystem about recording configurations.
33 */
34public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
35
36    public final static String TAG = "AudioService.RecordingActivityMonitor";
37
38    private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
39
40    private HashMap<Integer, AudioRecordConfiguration> mRecordConfigs =
41            new HashMap<Integer, AudioRecordConfiguration>();
42
43    RecordingActivityMonitor() {
44        RecMonitorClient.sMonitor = this;
45    }
46
47    /**
48     * Implementation of android.media.AudioSystem.AudioRecordingCallback
49     */
50    public void onRecordingConfigurationChanged(int event, int session, int source) {
51        if (updateSnapshot(event, session, source)) {
52            final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
53            synchronized(mClients) {
54                while (clientIterator.hasNext()) {
55                    try {
56                        clientIterator.next().mDispatcherCb.dispatchRecordingConfigChange();
57                    } catch (RemoteException e) {
58                        Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
59                    }
60                }
61            }
62        }
63    }
64
65    void initMonitor() {
66        AudioSystem.setRecordingCallback(this);
67    }
68
69    void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
70        if (rcdb == null) {
71            return;
72        }
73        synchronized(mClients) {
74            final RecMonitorClient rmc = new RecMonitorClient(rcdb);
75            if (rmc.init()) {
76                mClients.add(rmc);
77            }
78        }
79    }
80
81    void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
82        if (rcdb == null) {
83            return;
84        }
85        synchronized(mClients) {
86            final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
87            while (clientIterator.hasNext()) {
88                RecMonitorClient rmc = clientIterator.next();
89                if (rcdb.equals(rmc.mDispatcherCb)) {
90                    rmc.release();
91                    clientIterator.remove();
92                    break;
93                }
94            }
95        }
96    }
97
98    AudioRecordConfiguration[] getActiveRecordConfigurations() {
99        synchronized(mRecordConfigs) {
100            return mRecordConfigs.values().toArray(new AudioRecordConfiguration[0]);
101        }
102    }
103
104    /**
105     * Update the internal "view" of the active recording sessions
106     * @param event
107     * @param session
108     * @param source
109     * @return true if the list of active recording sessions has been modified, false otherwise.
110     */
111    private boolean updateSnapshot(int event, int session, int source) {
112        synchronized(mRecordConfigs) {
113            switch (event) {
114            case AudioManager.RECORD_CONFIG_EVENT_STOP:
115                // return failure if an unknown recording session stopped
116                return (mRecordConfigs.remove(new Integer(session)) != null);
117            case AudioManager.RECORD_CONFIG_EVENT_START:
118                if (mRecordConfigs.containsKey(new Integer(session))) {
119                    // start of session that's already tracked, not worth an update
120                    // TO DO in the future when tracking record format: there might be a record
121                    //       format change during a recording that requires reporting
122                    return false;
123                } else {
124                    mRecordConfigs.put(new Integer(session),
125                            new AudioRecordConfiguration(session, source));
126                    return true;
127                }
128            default:
129                Log.e(TAG, String.format("Unknown event %d for session %d, source %d",
130                        event, session, source));
131                return false;
132            }
133        }
134    }
135
136    /**
137     * Inner class to track clients that want to be notified of recording updates
138     */
139    private final static class RecMonitorClient implements IBinder.DeathRecipient {
140
141        // can afford to be static because only one RecordingActivityMonitor ever instantiated
142        static RecordingActivityMonitor sMonitor;
143
144        final IRecordingConfigDispatcher mDispatcherCb;
145
146        RecMonitorClient(IRecordingConfigDispatcher rcdb) {
147            mDispatcherCb = rcdb;
148        }
149
150        public void binderDied() {
151            Log.w(TAG, "client died");
152            sMonitor.unregisterRecordingCallback(mDispatcherCb);
153        }
154
155        boolean init() {
156            try {
157                mDispatcherCb.asBinder().linkToDeath(this, 0);
158                return true;
159            } catch (RemoteException e) {
160                Log.w(TAG, "Could not link to client death", e);
161                return false;
162            }
163        }
164
165        void release() {
166            mDispatcherCb.asBinder().unlinkToDeath(this, 0);
167        }
168    }
169}
170