17ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk/*
27ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * Copyright (C) 2011 The Android Open Source Project
37ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk *
47ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * Licensed under the Apache License, Version 2.0 (the "License");
57ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * you may not use this file except in compliance with the License.
67ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * You may obtain a copy of the License at
77ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk *
87ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk *      http://www.apache.org/licenses/LICENSE-2.0
97ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk *
107ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * Unless required by applicable law or agreed to in writing, software
117ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * distributed under the License is distributed on an "AS IS" BASIS,
127ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * See the License for the specific language governing permissions and
147ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * limitations under the License.
157ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk */
167ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
177ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkpackage com.android.settingslib.bluetooth;
187ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
197ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothA2dp;
207ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothAdapter;
217ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothClass;
227ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothDevice;
237ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothProfile;
247ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothUuid;
257ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.content.Context;
267ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.os.ParcelUuid;
277ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.util.Log;
287ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
297ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport com.android.settingslib.R;
307ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
317ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport java.util.ArrayList;
327ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport java.util.List;
337ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
347ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkpublic final class A2dpProfile implements LocalBluetoothProfile {
357ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private static final String TAG = "A2dpProfile";
367ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private static boolean V = false;
377ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
387ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private BluetoothA2dp mService;
397ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private boolean mIsProfileReady;
407ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
417ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private final LocalBluetoothAdapter mLocalAdapter;
427ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private final CachedBluetoothDeviceManager mDeviceManager;
437ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
447ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    static final ParcelUuid[] SINK_UUIDS = {
457ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        BluetoothUuid.AudioSink,
467ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        BluetoothUuid.AdvAudioDist,
477ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    };
487ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
497ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    static final String NAME = "A2DP";
507ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private final LocalBluetoothProfileManager mProfileManager;
517ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
527ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    // Order of this profile in device profiles list
537ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private static final int ORDINAL = 1;
547ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
557ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    // These callbacks run on the main thread.
567ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private final class A2dpServiceListener
577ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            implements BluetoothProfile.ServiceListener {
587ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
597ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        public void onServiceConnected(int profile, BluetoothProfile proxy) {
607ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            if (V) Log.d(TAG,"Bluetooth service connected");
617ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mService = (BluetoothA2dp) proxy;
627ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            // We just bound to the service, so refresh the UI for any connected A2DP devices.
637ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
647ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            while (!deviceList.isEmpty()) {
657ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                BluetoothDevice nextDevice = deviceList.remove(0);
667ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
677ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                // we may add a new device here, but generally this should not happen
687ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                if (device == null) {
697ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                    Log.w(TAG, "A2dpProfile found new device: " + nextDevice);
707ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
717ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                }
727ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED);
737ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                device.refresh();
747ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
757ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mIsProfileReady=true;
767ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
777ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
787ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        public void onServiceDisconnected(int profile) {
797ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            if (V) Log.d(TAG,"Bluetooth service disconnected");
807ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mIsProfileReady=false;
817ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
827ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
837ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
847ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean isProfileReady() {
857ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mIsProfileReady;
867ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
877ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
887ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    A2dpProfile(Context context, LocalBluetoothAdapter adapter,
897ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            CachedBluetoothDeviceManager deviceManager,
907ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            LocalBluetoothProfileManager profileManager) {
917ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        mLocalAdapter = adapter;
927ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        mDeviceManager = deviceManager;
937ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        mProfileManager = profileManager;
947ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
957ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                BluetoothProfile.A2DP);
967ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
977ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
987ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean isConnectable() {
997ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return true;
1007ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1017ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1027ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean isAutoConnectable() {
1037ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return true;
1047ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1057ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1067ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public List<BluetoothDevice> getConnectedDevices() {
1077ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return new ArrayList<BluetoothDevice>(0);
1087ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.getDevicesMatchingConnectionStates(
1097ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk              new int[] {BluetoothProfile.STATE_CONNECTED,
1107ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                         BluetoothProfile.STATE_CONNECTING,
1117ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                         BluetoothProfile.STATE_DISCONNECTING});
1127ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1137ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1147ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean connect(BluetoothDevice device) {
1157ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return false;
1167ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        List<BluetoothDevice> sinks = getConnectedDevices();
1177ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (sinks != null) {
1187ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            for (BluetoothDevice sink : sinks) {
1197ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                mService.disconnect(sink);
1207ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
1217ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1227ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.connect(device);
1237ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1247ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1257ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean disconnect(BluetoothDevice device) {
1267ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return false;
1277ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        // Downgrade priority as user is disconnecting the headset.
1287ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
1297ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
1307ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1317ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.disconnect(device);
1327ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1337ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1347ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getConnectionStatus(BluetoothDevice device) {
1357ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) {
1367ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            return BluetoothProfile.STATE_DISCONNECTED;
1377ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1387ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.getConnectionState(device);
1397ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1407ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1417ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean isPreferred(BluetoothDevice device) {
1427ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return false;
1437ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
1447ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1457ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1467ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getPreferred(BluetoothDevice device) {
1477ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
1487ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.getPriority(device);
1497ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1507ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1517ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public void setPreferred(BluetoothDevice device, boolean preferred) {
1527ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return;
1537ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (preferred) {
1547ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
1557ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
1567ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
1577ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        } else {
1587ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
1597ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1607ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1617ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    boolean isA2dpPlaying() {
1627ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return false;
1637ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        List<BluetoothDevice> sinks = mService.getConnectedDevices();
1647ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (!sinks.isEmpty()) {
1657ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            if (mService.isA2dpPlaying(sinks.get(0))) {
1667ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                return true;
1677ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
1687ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1697ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return false;
1707ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1717ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1727ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public String toString() {
1737ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return NAME;
1747ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1757ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1767ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getOrdinal() {
1777ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return ORDINAL;
1787ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1797ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1807ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getNameResource(BluetoothDevice device) {
1817ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return R.string.bluetooth_profile_a2dp;
1827ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1837ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1847ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getSummaryResourceForDevice(BluetoothDevice device) {
1857ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        int state = getConnectionStatus(device);
1867ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        switch (state) {
1877ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            case BluetoothProfile.STATE_DISCONNECTED:
1887ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                return R.string.bluetooth_a2dp_profile_summary_use_for;
1897ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1907ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            case BluetoothProfile.STATE_CONNECTED:
1917ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                return R.string.bluetooth_a2dp_profile_summary_connected;
1927ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1937ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            default:
1947ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                return Utils.getConnectionStateSummary(state);
1957ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1967ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1977ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1987ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getDrawableResource(BluetoothClass btClass) {
1997ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return R.drawable.ic_bt_headphones_a2dp;
2007ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
2017ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
2027ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    protected void finalize() {
2037ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (V) Log.d(TAG, "finalize()");
2047ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService != null) {
2057ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            try {
2067ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,
2077ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                                                                       mService);
2087ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                mService = null;
2097ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }catch (Throwable t) {
2107ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                Log.w(TAG, "Error cleaning up A2DP proxy", t);
2117ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
2127ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
2137ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
2147ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk}
215