1aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad/*
2aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * Copyright (C) 2014 The Android Open Source Project
3aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad *
4aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * Licensed under the Apache License, Version 2.0 (the "License");
5aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * you may not use this file except in compliance with the License.
6aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * You may obtain a copy of the License at
7aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad *
8aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad *      http://www.apache.org/licenses/LICENSE-2.0
9aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad *
10aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * Unless required by applicable law or agreed to in writing, software
11aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * distributed under the License is distributed on an "AS IS" BASIS,
12aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * See the License for the specific language governing permissions and
14aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * limitations under the License.
15aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad */
16aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
17aa7340388473c1676495a60e30dc6a48d318a489Ihab Awadpackage com.android.services.telephony;
18aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
19aa7340388473c1676495a60e30dc6a48d318a489Ihab Awadimport java.util.ArrayList;
206059c9310c85986f38c22940470e5a0ff280806fSantos Cordonimport java.util.Collections;
2153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordonimport java.util.HashSet;
22aa7340388473c1676495a60e30dc6a48d318a489Ihab Awadimport java.util.List;
2353b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordonimport java.util.Set;
24aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
254d45d1cf58a2003378fd35912d6d73a00001bf06Tyler Gunnimport android.telecom.Conference;
264d45d1cf58a2003378fd35912d6d73a00001bf06Tyler Gunnimport android.telecom.Connection;
27aef7a4bc4f85149de427d7506ebe97753b2ca6c2Andrew Leeimport android.telecom.DisconnectCause;
28aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
2953b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordonimport com.android.internal.telephony.Call;
3053b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon
31aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad/**
32aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * Maintains a list of all the known GSM connections and implements GSM-specific conference
33aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad * call functionality.
34aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad */
353199aa7f8bcb48569eb8289abc055ba0f8496ba8Sailesh Nepalfinal class GsmConferenceController {
36079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad    private static final int GSM_CONFERENCE_MAX_SIZE = 5;
37079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad
382093a451b17c26f4341e9565b65dcaa0e20bbd7dSailesh Nepal    private final Connection.Listener mConnectionListener = new Connection.Listener() {
39af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        @Override
40af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        public void onStateChanged(Connection c, int state) {
41af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen            recalculate();
42af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        }
43aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
44af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        /** ${inheritDoc} */
45af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        @Override
46aef7a4bc4f85149de427d7506ebe97753b2ca6c2Andrew Lee        public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
47af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen            recalculate();
48af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        }
49af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen
50af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        @Override
51af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        public void onDestroyed(Connection connection) {
52af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen            remove((GsmConnection) connection);
53af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen        }
54af4b3fd88b5412fbd644b1a4b9d1909c25681d4cNancy Chen    };
55aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
56aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad    /** The known GSM connections. */
57aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad    private final List<GsmConnection> mGsmConnections = new ArrayList<>();
58aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
5953b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon    private final TelephonyConnectionService mConnectionService;
60aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
6153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon    public GsmConferenceController(TelephonyConnectionService connectionService) {
6253b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        mConnectionService = connectionService;
63aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad    }
64aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
6553b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon    /** The GSM conference connection object. */
6653b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon    private Conference mGsmConference;
67aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
6853b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon    void add(GsmConnection connection) {
6953b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        mGsmConnections.add(connection);
7053b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        connection.addConnectionListener(mConnectionListener);
7153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        recalculate();
723199aa7f8bcb48569eb8289abc055ba0f8496ba8Sailesh Nepal    }
733199aa7f8bcb48569eb8289abc055ba0f8496ba8Sailesh Nepal
7453b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon    void remove(GsmConnection connection) {
7553b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        connection.removeConnectionListener(mConnectionListener);
7653b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        mGsmConnections.remove(connection);
7753b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        recalculate();
78aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad    }
79aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
806059c9310c85986f38c22940470e5a0ff280806fSantos Cordon    private void recalculate() {
816059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        recalculateConferenceable();
8253b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        recalculateConference();
836059c9310c85986f38c22940470e5a0ff280806fSantos Cordon    }
846059c9310c85986f38c22940470e5a0ff280806fSantos Cordon
85079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad    private boolean isFullConference(Conference conference) {
86079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad        return conference.getConnections().size() >= GSM_CONFERENCE_MAX_SIZE;
87079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad    }
88079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad
89079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad    private boolean participatesInFullConference(Connection connection) {
90079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad        return connection.getConference() != null &&
91079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad                isFullConference(connection.getConference());
92079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad    }
93079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad
94aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad    /**
95aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad     * Calculates the conference-capable state of all GSM connections in this connection service.
96aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad     */
976059c9310c85986f38c22940470e5a0ff280806fSantos Cordon    private void recalculateConferenceable() {
986059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        Log.v(this, "recalculateConferenceable : %d", mGsmConnections.size());
996059c9310c85986f38c22940470e5a0ff280806fSantos Cordon
1006059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        List<Connection> activeConnections = new ArrayList<>(mGsmConnections.size());
1016059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        List<Connection> backgroundConnections = new ArrayList<>(mGsmConnections.size());
1026059c9310c85986f38c22940470e5a0ff280806fSantos Cordon
1036059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        // Loop through and collect all calls which are active or holding
104aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad        for (GsmConnection connection : mGsmConnections) {
105079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad            Log.d(this, "recalc - %s %s", connection.getState(), connection);
106079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad
107079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad            if (!participatesInFullConference(connection)) {
108079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad                switch (connection.getState()) {
109079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad                    case Connection.STATE_ACTIVE:
1106059c9310c85986f38c22940470e5a0ff280806fSantos Cordon                        activeConnections.add(connection);
111079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad                        continue;
112079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad                    case Connection.STATE_HOLDING:
1136059c9310c85986f38c22940470e5a0ff280806fSantos Cordon                        backgroundConnections.add(connection);
114079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad                        continue;
1156059c9310c85986f38c22940470e5a0ff280806fSantos Cordon                    default:
1166059c9310c85986f38c22940470e5a0ff280806fSantos Cordon                        break;
117aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad                }
118aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad            }
119079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad
120079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad            connection.setConferenceableConnections(Collections.<Connection>emptyList());
1216059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        }
1226059c9310c85986f38c22940470e5a0ff280806fSantos Cordon
1236059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        Log.v(this, "active: %d, holding: %d",
1246059c9310c85986f38c22940470e5a0ff280806fSantos Cordon                activeConnections.size(), backgroundConnections.size());
1256059c9310c85986f38c22940470e5a0ff280806fSantos Cordon
1266059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        // Go through all the active connections and set the background connections as
1276059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        // conferenceable.
1286059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        for (Connection connection : activeConnections) {
1296059c9310c85986f38c22940470e5a0ff280806fSantos Cordon            connection.setConferenceableConnections(backgroundConnections);
1306059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        }
131aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad
1326059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        // Go through all the background connections and set the active connections as
1336059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        // conferenceable.
1346059c9310c85986f38c22940470e5a0ff280806fSantos Cordon        for (Connection connection : backgroundConnections) {
1356059c9310c85986f38c22940470e5a0ff280806fSantos Cordon            connection.setConferenceableConnections(activeConnections);
136aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad        }
1373197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad
1383197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad        // Set the conference as conferenceable with all the connections
139079bd15c102a656e570aba53f30c73118e75ac95Ihab Awad        if (mGsmConference != null && !isFullConference(mGsmConference)) {
1403197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad            List<Connection> nonConferencedConnections = new ArrayList<>(mGsmConnections.size());
1413197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad            for (GsmConnection c : mGsmConnections) {
1423197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad                if (c.getConference() == null) {
1433197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad                    nonConferencedConnections.add(c);
1443197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad                }
1453197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad            }
1463197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad            mGsmConference.setConferenceableConnections(nonConferencedConnections);
1473197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad        }
1483197ea821dcdee1c30549f850b99c2cfc7f668cbIhab Awad
14953b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        // TODO: Do not allow conferencing of already conferenced connections.
15053b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon    }
15153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon
15253b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon    private void recalculateConference() {
15353b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        Set<GsmConnection> conferencedConnections = new HashSet<>();
15453b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon
15553b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        for (GsmConnection connection : mGsmConnections) {
15653b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            com.android.internal.telephony.Connection radioConnection =
15753b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                connection.getOriginalConnection();
15853b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon
15953b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            if (radioConnection != null) {
16053b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                Call.State state = radioConnection.getState();
16153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                Call call = radioConnection.getCall();
16253b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) &&
16353b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                        (call != null && call.isMultiparty())) {
16453b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                    conferencedConnections.add(connection);
16553b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                }
16653b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            }
16753b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        }
16853b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon
16953b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        Log.d(this, "Recalculate conference calls %s %s.",
17053b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                mGsmConference, conferencedConnections);
17153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon
17253b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        if (conferencedConnections.size() < 2) {
17353b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            Log.d(this, "less than two conference calls!");
17453b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            // No more connections are conferenced, destroy any existing conference.
17553b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            if (mGsmConference != null) {
17653b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                Log.d(this, "with a conference to destroy!");
17753b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                mGsmConference.destroy();
17853b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                mGsmConference = null;
17953b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            }
18053b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        } else {
18153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            if (mGsmConference != null) {
18253b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                List<Connection> existingConnections = mGsmConference.getConnections();
18353b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                // Remove any that no longer exist
18453b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                for (Connection connection : existingConnections) {
18553b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                    if (!conferencedConnections.contains(connection)) {
18653b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                        mGsmConference.removeConnection(connection);
18753b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                    }
18853b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                }
18953b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon
19053b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                // Add any new ones
19153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                for (Connection connection : conferencedConnections) {
19253b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                    if (!existingConnections.contains(connection)) {
19353b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                        mGsmConference.addConnection(connection);
19453b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                    }
19553b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                }
19653b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            } else {
19753b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                mGsmConference = new GsmConference(null);
19853b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                for (Connection connection : conferencedConnections) {
1991c25892dc5f0170441422f15242df8887b7903edSantos Cordon                    Log.d(this, "Adding a connection to a conference call: %s %s",
2001c25892dc5f0170441422f15242df8887b7903edSantos Cordon                            mGsmConference, connection);
20153b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                    mGsmConference.addConnection(connection);
20253b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                }
20353b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon                mConnectionService.addConference(mGsmConference);
20453b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon            }
2058a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee
2068a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee            // Set the conference state to the same state as its child connections.
2078a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee            Connection conferencedConnection = mGsmConference.getConnections().get(0);
2088a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee            switch (conferencedConnection.getState()) {
2098a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee                case Connection.STATE_ACTIVE:
2108a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee                    mGsmConference.setActive();
2118a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee                    break;
2128a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee                case Connection.STATE_HOLDING:
2138a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee                    mGsmConference.setOnHold();
2148a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee                    break;
2158a76b6b8715188f00fff4c0e52d579f10cc53de3Andrew Lee            }
21653b84fe2dc796ef172d7c0f4b9bdc177cdeb0c0fSantos Cordon        }
217aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad    }
218aa7340388473c1676495a60e30dc6a48d318a489Ihab Awad}
219