17ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk/*
27ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * Copyright (C) 2012 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.BluetoothAdapter;
207ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothClass;
217ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothDevice;
227ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkimport android.bluetooth.BluetoothMap;
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 Monk/**
357ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk * MapProfile handles Bluetooth MAP profile.
367ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk */
377ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monkpublic final class MapProfile implements LocalBluetoothProfile {
387ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private static final String TAG = "MapProfile";
397ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private static boolean V = true;
407ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
417ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private BluetoothMap mService;
427ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private boolean mIsProfileReady;
437ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
447ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private final LocalBluetoothAdapter mLocalAdapter;
457ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private final CachedBluetoothDeviceManager mDeviceManager;
467ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private final LocalBluetoothProfileManager mProfileManager;
477ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
487ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    static final ParcelUuid[] UUIDS = {
497ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        BluetoothUuid.MAP,
507ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        BluetoothUuid.MNS,
517ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        BluetoothUuid.MAS,
527ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    };
537ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
547ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    static final String NAME = "MAP";
557ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
567ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    // Order of this profile in device profiles list
577ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
587ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    // These callbacks run on the main thread.
597ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    private final class MapServiceListener
607ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            implements BluetoothProfile.ServiceListener {
617ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
627ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        public void onServiceConnected(int profile, BluetoothProfile proxy) {
637ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            if (V) Log.d(TAG,"Bluetooth service connected");
647ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mService = (BluetoothMap) proxy;
657ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            // We just bound to the service, so refresh the UI for any connected MAP devices.
667ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
677ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            while (!deviceList.isEmpty()) {
687ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                BluetoothDevice nextDevice = deviceList.remove(0);
697ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
707ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                // we may add a new device here, but generally this should not happen
717ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                if (device == null) {
727ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                    Log.w(TAG, "MapProfile found new device: " + nextDevice);
737ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
747ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                }
757ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                device.onProfileStateChanged(MapProfile.this,
767ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                        BluetoothProfile.STATE_CONNECTED);
777ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                device.refresh();
787ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
797ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
807ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mProfileManager.callServiceConnectedListeners();
817ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mIsProfileReady=true;
827ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
837ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
847ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        public void onServiceDisconnected(int profile) {
857ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            if (V) Log.d(TAG,"Bluetooth service disconnected");
867ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mProfileManager.callServiceDisconnectedListeners();
877ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mIsProfileReady=false;
887ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
897ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
907ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
917ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean isProfileReady() {
927ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if(V) Log.d(TAG,"isProfileReady(): "+ mIsProfileReady);
937ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mIsProfileReady;
947ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
957ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
967ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    MapProfile(Context context, LocalBluetoothAdapter adapter,
977ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            CachedBluetoothDeviceManager deviceManager,
987ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            LocalBluetoothProfileManager profileManager) {
997ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        mLocalAdapter = adapter;
1007ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        mDeviceManager = deviceManager;
1017ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        mProfileManager = profileManager;
1027ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        mLocalAdapter.getProfileProxy(context, new MapServiceListener(),
1037ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                BluetoothProfile.MAP);
1047ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1057ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1067ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean isConnectable() {
1077ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return true;
1087ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1097ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1107ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean isAutoConnectable() {
1117ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return true;
1127ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1137ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1147ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean connect(BluetoothDevice device) {
1157ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if(V)Log.d(TAG,"connect() - should not get called");
1167ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return false; // MAP never connects out
1177ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1187ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1197ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean disconnect(BluetoothDevice device) {
1207ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return false;
1217ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
1227ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) {
1237ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
1247ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
1257ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
1267ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            return mService.disconnect(device);
1277ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        } else {
1287ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            return false;
1297ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1307ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1317ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1327ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getConnectionStatus(BluetoothDevice device) {
1337ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
1347ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        List<BluetoothDevice> deviceList = mService.getConnectedDevices();
1357ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if(V) Log.d(TAG,"getConnectionStatus: status is: "+ mService.getConnectionState(device));
1367ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1377ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return !deviceList.isEmpty() && deviceList.get(0).equals(device)
1387ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                ? mService.getConnectionState(device)
1397ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                : BluetoothProfile.STATE_DISCONNECTED;
1407ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1417ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1427ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public boolean isPreferred(BluetoothDevice device) {
1437ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return false;
1447ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
1457ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1467ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1477ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getPreferred(BluetoothDevice device) {
1487ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
1497ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.getPriority(device);
1507ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1517ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1527ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public void setPreferred(BluetoothDevice device, boolean preferred) {
1537ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return;
1547ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (preferred) {
1557ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
1567ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
1577ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
1587ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        } else {
1597ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
1607ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1617ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1627ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1637ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public List<BluetoothDevice> getConnectedDevices() {
1647ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService == null) return new ArrayList<BluetoothDevice>(0);
1657ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return mService.getDevicesMatchingConnectionStates(
1667ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk              new int[] {BluetoothProfile.STATE_CONNECTED,
1677ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                         BluetoothProfile.STATE_CONNECTING,
1687ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                         BluetoothProfile.STATE_DISCONNECTING});
1697ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1707ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1717ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public String toString() {
1727ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return NAME;
1737ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1747ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1757ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getOrdinal() {
1767ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return BluetoothProfile.MAP;
1777ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1787ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1797ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getNameResource(BluetoothDevice device) {
1807ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return R.string.bluetooth_profile_map;
1817ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1827ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1837ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getSummaryResourceForDevice(BluetoothDevice device) {
1847ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        int state = getConnectionStatus(device);
1857ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        switch (state) {
1867ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            case BluetoothProfile.STATE_DISCONNECTED:
1877ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                return R.string.bluetooth_map_profile_summary_use_for;
1887ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1897ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            case BluetoothProfile.STATE_CONNECTED:
1907ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                return R.string.bluetooth_map_profile_summary_connected;
1917ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1927ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            default:
1937ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                return Utils.getConnectionStateSummary(state);
1947ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
1957ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
1967ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
1977ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    public int getDrawableResource(BluetoothClass btClass) {
1987ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        return R.drawable.ic_bt_cellphone;
1997ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
2007ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk
2017ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    protected void finalize() {
2027ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (V) Log.d(TAG, "finalize()");
2037ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        if (mService != null) {
2047ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            try {
2057ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.MAP,
2067ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                                                                       mService);
2077ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                mService = null;
2087ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }catch (Throwable t) {
2097ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk                Log.w(TAG, "Error cleaning up MAP proxy", t);
2107ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk            }
2117ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk        }
2127ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk    }
2137ce96b9e610de2782ec5f2af806e7bc0f90c8578Jason Monk}
214