Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1fe47e3b authored by Jack Yu's avatar Jack Yu Committed by android-build-merger
Browse files

Merge "Set country code even when device is not registered on network"

am: 2a7f19d2

Change-Id: Ied5f9ac69e8c7777f38c86ae6b66de5d88432293
parents ac4d72a2 2a7f19d2
Loading
Loading
Loading
Loading
+3 −6
Original line number Diff line number Diff line
@@ -337,7 +337,7 @@ public class GsmCdmaPhone extends Phone {
                setIsoCountryProperty(operatorNumeric);
                // Updates MCC MNC device configuration information
                logd("update mccmnc=" + operatorNumeric);
                MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
                MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
            }

            // Sets current entry in the telephony carrier table
@@ -358,10 +358,7 @@ public class GsmCdmaPhone extends Phone {
        } else {
            String iso = "";
            try {
                iso = MccTable.countryCodeForMcc(Integer.parseInt(
                        operatorNumeric.substring(0,3)));
            } catch (NumberFormatException ex) {
                Rlog.e(LOG_TAG, "setIsoCountryProperty: countryCodeForMcc error", ex);
                iso = MccTable.countryCodeForMcc(operatorNumeric.substring(0, 3));
            } catch (StringIndexOutOfBoundsException ex) {
                Rlog.e(LOG_TAG, "setIsoCountryProperty: countryCodeForMcc error", ex);
            }
@@ -2709,7 +2706,7 @@ public class GsmCdmaPhone extends Phone {

                    // Updates MCC MNC device configuration information
                    logd("update mccmnc=" + operatorNumeric);
                    MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
                    MccTable.updateMccMncConfiguration(mContext, operatorNumeric);

                    return true;
                } catch (SQLException e) {
+345 −0
Original line number Diff line number Diff line
/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony;

import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.telephony.CellInfo;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.LocalLog;

import com.android.internal.util.IndentingPrintWriter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * The locale tracker keeps tracking the current locale of the phone.
 */
public class LocaleTracker extends Handler {
    private static final boolean DBG = true;
    private static final String TAG = LocaleTracker.class.getSimpleName();

    /** Event to trigger get cell info from the modem */
    private static final int EVENT_GET_CELL_INFO = 1;

    // Todo: Read this from Settings.
    /** The minimum delay to get cell info from the modem */
    private static final long CELL_INFO_MIN_DELAY_MS = 2 * SECOND_IN_MILLIS;

    // Todo: Read this from Settings.
    /** The maximum delay to get cell info from the modem */
    private static final long CELL_INFO_MAX_DELAY_MS = 1 * HOUR_IN_MILLIS;

    private final Phone mPhone;

    /** SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX */
    private int mSimState;

    /** Current serving PLMN's MCC/MNC */
    @Nullable
    private String mOperatorNumeric;

    /** Current cell tower information */
    @Nullable
    private List<CellInfo> mCellInfo;

    /** Count of invalid cell info we've got so far. Will reset once we get a successful one */
    private int mFailCellInfoCount;

    /** The ISO-3166 code of device's current country */
    @Nullable
    private String mCurrentCountryIso;

    private final LocalLog mLocalLog = new LocalLog(50);

    /** Broadcast receiver to get SIM card state changed event */
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED.equals(intent.getAction())) {
                int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0);
                if (phoneId == mPhone.getPhoneId()) {
                    onSimCardStateChanged(intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
                            TelephonyManager.SIM_STATE_UNKNOWN));
                }
            }
        }
    };

    /**
     * Message handler
     *
     * @param msg The message
     */
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case EVENT_GET_CELL_INFO:
                synchronized (this) {
                    getCellInfo();
                    updateLocale();
                }
                break;
            default:
                throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
        }
    }

    /**
     * Constructor
     *
     * @param phone The phone object
     * @param looper The looper message handler
     */
    public LocaleTracker(Phone phone, Looper looper)  {
        super(looper);
        mPhone = phone;
        mSimState = TelephonyManager.SIM_STATE_UNKNOWN;

        final IntentFilter filter = new IntentFilter();
        filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
        mPhone.getContext().registerReceiver(mBroadcastReceiver, filter);
    }

    /**
     * Get the device's current country.
     *
     * @return The device's current country. Empty string if the information is not available.
     */
    @NonNull
    public synchronized String getCurrentCountry() {
        return (mCurrentCountryIso != null) ? mCurrentCountryIso : "";
    }

    /**
     * Get the MCC from cell tower information.
     *
     * @return MCC in string format. Null if the information is not available.
     */
    @Nullable
    private String getMccFromCellInfo() {
        String selectedMcc = null;
        if (mCellInfo != null) {
            Map<String, Integer> countryCodeMap = new HashMap<>();
            int maxCount = 0;
            for (CellInfo cellInfo : mCellInfo) {
                String mcc = null;
                if (cellInfo instanceof CellInfoGsm) {
                    mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMccString();
                } else if (cellInfo instanceof CellInfoLte) {
                    mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMccString();
                } else if (cellInfo instanceof CellInfoWcdma) {
                    mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMccString();
                }
                if (mcc != null) {
                    int count = 1;
                    if (countryCodeMap.containsKey(mcc)) {
                        count = countryCodeMap.get(mcc) + 1;
                    }
                    countryCodeMap.put(mcc, count);
                    // This is unlikely, but if MCC from cell info looks different, we choose the
                    // MCC that occurs most.
                    if (count > maxCount) {
                        maxCount = count;
                        selectedMcc = mcc;
                    }
                }
            }
        }
        return selectedMcc;
    }

    /**
     * Called when SIM card state changed. Only when we absolutely know the SIM is absent, we get
     * cell info from the network. Other SIM states like NOT_READY might be just a transitioning
     * state.
     *
     * @param state SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX.
     */
    private synchronized void onSimCardStateChanged(int state) {
        if (mSimState != state && state == TelephonyManager.SIM_STATE_ABSENT) {
            if (DBG) log("Sim absent. Get latest cell info from the modem.");
            getCellInfo();
            updateLocale();
        }
        mSimState = state;
    }

    /**
     * Update MCC/MNC from network service state.
     *
     * @param operatorNumeric MCC/MNC of the operator
     */
    public synchronized void updateOperatorNumeric(String operatorNumeric) {
        // Check if the operator numeric changes.
        String msg = "updateOperatorNumeric. mcc/mnc=" + operatorNumeric;
        if (DBG) log(msg);
        mLocalLog.log(msg);
        if (!Objects.equals(mOperatorNumeric, operatorNumeric)) {
            if (DBG) {
                log("onUpdateOperatorNumeric: operator numeric changes to " + operatorNumeric);
            }
            mOperatorNumeric = operatorNumeric;

            // If the operator numeric becomes unavailable, we need to get the latest cell info so
            // that we can get MCC from it.
            if (TextUtils.isEmpty(mOperatorNumeric)) {
                if (DBG) {
                    log("Operator numeric unavailable. Get latest cell info from the modem.");
                }
                getCellInfo();
            }
            updateLocale();
        }
    }

    /**
     * Get the delay time to get cell info from modem. The delay time grows exponentially to prevent
     * battery draining.
     *
     * @param failCount Count of invalid cell info we've got so far.
     * @return The delay time for next get cell info
     */
    private long getCellInfoDelayTime(int failCount) {
        // Exponentially grow the delay time
        long delay = CELL_INFO_MIN_DELAY_MS * (long) Math.pow(2, failCount - 1);
        if (delay < CELL_INFO_MIN_DELAY_MS) {
            delay = CELL_INFO_MIN_DELAY_MS;
        } else if (delay > CELL_INFO_MAX_DELAY_MS) {
            delay = CELL_INFO_MAX_DELAY_MS;
        }
        return delay;
    }

    /**
     * Get cell info from the modem.
     */
    private void getCellInfo() {
        // Get all cell info. Passing null to use default worksource, which indicates the original
        // request is from telephony internally.
        mCellInfo = mPhone.getAllCellInfo(null);
        String msg = "getCellInfo: cell info=" + mCellInfo;
        if (DBG) log(msg);
        mLocalLog.log(msg);
        if (mCellInfo == null || mCellInfo.size() == 0) {
            // If we can't get a valid cell info. Try it again later.
            long delay = getCellInfoDelayTime(++mFailCellInfoCount);
            if (DBG) log("Can't get cell info. Try again in " + delay / 1000 + " secs.");
            sendMessageDelayed(obtainMessage(EVENT_GET_CELL_INFO), delay);
        } else {
            mFailCellInfoCount = 0;
            // We successfully got cell info from the modem. Cancel the queued get cell info event
            // if there is any.
            removeMessages(EVENT_GET_CELL_INFO);
        }
    }

    /**
     * Update the device's current locale
     */
    private void updateLocale() {
        // If MCC is available from network service state, use it first.
        String mcc = null;
        String countryIso = null;
        if (!TextUtils.isEmpty(mOperatorNumeric)) {
            try {
                mcc = mOperatorNumeric.substring(0, 3);
                countryIso = MccTable.countryCodeForMcc(mcc);
            } catch (StringIndexOutOfBoundsException ex) {
                loge("updateLocale: Can't get country from operator numeric. mcc = "
                        + mcc + ". ex=" + ex);
            }
        }

        // If for any reason we can't get country from operator numeric, try to get it from cell
        // info.
        if (TextUtils.isEmpty(countryIso)) {
            mcc = getMccFromCellInfo();
            countryIso = MccTable.countryCodeForMcc(mcc);
        }

        String msg = "updateLocale: mcc = " + mcc + ", country = " + countryIso;
        log(msg);
        mLocalLog.log(msg);
        if (!Objects.equals(countryIso, mCurrentCountryIso)) {
            log("updateLocale: Change the current country to " + countryIso);
            mCurrentCountryIso = countryIso;

            TelephonyManager.setTelephonyProperty(mPhone.getPhoneId(),
                    TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, mCurrentCountryIso);

            // Set the country code for wifi. This sets allowed wifi channels based on the
            // country of the carrier we see. If we can't see any, reset to 0 so we don't
            // broadcast on forbidden channels.
            ((WifiManager) mPhone.getContext().getSystemService(Context.WIFI_SERVICE))
                    .setCountryCode(countryIso, false);
        }
    }

    private void log(String msg) {
        Rlog.d(TAG, msg);
    }

    private void loge(String msg) {
        Rlog.e(TAG, msg);
    }

    /**
     * Print the DeviceStateMonitor into the given stream.
     *
     * @param fd The raw file descriptor that the dump is being sent to.
     * @param pw A PrintWriter to which the dump is to be set.
     * @param args Additional arguments to the dump request.
     */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
        pw.println("LocaleTracker:");
        ipw.increaseIndent();
        ipw.println("mOperatorNumeric = " + mOperatorNumeric);
        ipw.println("mSimState = " + mSimState);
        ipw.println("mCellInfo = " + mCellInfo);
        ipw.println("mCurrentCountryIso = " + mCurrentCountryIso);
        ipw.println("mFailCellInfoCount = " + mFailCellInfoCount);
        ipw.println("Local logs:");
        ipw.increaseIndent();
        mLocalLog.dump(fd, ipw, args);
        ipw.decreaseIndent();
        ipw.decreaseIndent();
        ipw.flush();
    }
}
+34 −56
Original line number Diff line number Diff line
@@ -19,11 +19,9 @@ package com.android.internal.telephony;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Slog;

@@ -112,6 +110,19 @@ public final class MccTable {
        }
    }

    /**
     * Given a GSM Mobile Country Code, returns
     * an ISO two-character country code if available.
     * Returns empty string if unavailable.
     */
    public static String countryCodeForMcc(String mcc) {
        try {
            return countryCodeForMcc(Integer.parseInt(mcc));
        } catch (NumberFormatException ex) {
            return "";
        }
    }

    /**
     * Given a GSM Mobile Country Code, returns
     * an ISO 2-3 character language code if available.
@@ -159,11 +170,9 @@ public final class MccTable {
     * correct version of resources.  If MCC is 0, MCC and MNC will be ignored (not set).
     * @param context Context to act on.
     * @param mccmnc truncated imsi with just the MCC and MNC - MNC assumed to be from 4th to end
     * @param fromServiceState true if coming from the radio service state, false if from SIM
     */
    public static void updateMccMncConfiguration(Context context, String mccmnc,
            boolean fromServiceState) {
        Slog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc + "' fromServiceState=" + fromServiceState);
    public static void updateMccMncConfiguration(Context context, String mccmnc) {
        Slog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc);

        if (Build.IS_DEBUGGABLE) {
            String overrideMcc = SystemProperties.get("persist.sys.override_mcc");
@@ -176,19 +185,11 @@ public final class MccTable {
        if (!TextUtils.isEmpty(mccmnc)) {
            int mcc, mnc;

            String defaultMccMnc = TelephonyManager.getDefault().getSimOperatorNumeric();
            Slog.d(LOG_TAG, "updateMccMncConfiguration defaultMccMnc=" + defaultMccMnc);
            //Update mccmnc only for default subscription in case of MultiSim.
//            if (!defaultMccMnc.equals(mccmnc)) {
//                Slog.d(LOG_TAG, "Not a Default subscription, ignoring mccmnc config update.");
//                return;
//            }

            try {
                mcc = Integer.parseInt(mccmnc.substring(0, 3));
                mnc = Integer.parseInt(mccmnc.substring(3));
            } catch (NumberFormatException e) {
                Slog.e(LOG_TAG, "Error parsing IMSI: " + mccmnc);
            } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
                Slog.e(LOG_TAG, "Error parsing IMSI: " + mccmnc + ". ex=" + ex);
                return;
            }

@@ -196,10 +197,7 @@ public final class MccTable {
            if (mcc != 0) {
                setTimezoneFromMccIfNeeded(context, mcc);
            }
            if (fromServiceState) {
                setWifiCountryCodeFromMcc(context, mcc);
            } else {
                // from SIM

            try {
                Configuration config = new Configuration();
                boolean updateConfig = false;
@@ -219,12 +217,6 @@ public final class MccTable {
                Slog.e(LOG_TAG, "Can't update configuration", e);
            }
        }
        } else {
            if (fromServiceState) {
                // an empty mccmnc means no signal - tell wifi we don't know
                setWifiCountryCodeFromMcc(context, 0);
            }
        }
    }

    /**
@@ -396,20 +388,6 @@ public final class MccTable {
        return locale;
    }

    /**
     * Set the country code for wifi.  This sets allowed wifi channels based on the
     * country of the carrier we see.  If we can't see any, reset to 0 so we don't
     * broadcast on forbidden channels.
     * @param context Context to act on.
     * @param mcc Mobile Country Code of the operator.  0 if not known
     */
    private static void setWifiCountryCodeFromMcc(Context context, int mcc) {
        String country = MccTable.countryCodeForMcc(mcc);
        Slog.d(LOG_TAG, "WIFI_COUNTRY_CODE set to " + country);
        WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        wM.setCountryCode(country, false);
    }

    static {
        sTable = new ArrayList<MccEntry>(240);

+26 −23
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.AsyncResult;
import android.os.BaseBundle;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.Registrant;
@@ -259,6 +260,9 @@ public class ServiceStateTracker extends Handler {

    private final RatRatcheter mRatRatcheter;

    private final HandlerThread mHandlerThread;
    private final LocaleTracker mLocaleTracker;

    private final LocalLog mRoamingLog = new LocalLog(10);
    private final LocalLog mAttachLog = new LocalLog(10);
    private final LocalLog mPhoneTypeLog = new LocalLog(10);
@@ -508,6 +512,13 @@ public class ServiceStateTracker extends Handler {
                    this, EVENT_NETWORK_STATE_CHANGED, null);
        }

        // Create a new handler thread dedicated for locale tracker because the blocking
        // getAllCellInfo call requires clients calling from a different thread.
        mHandlerThread = new HandlerThread(LocaleTracker.class.getSimpleName());
        mHandlerThread.start();
        mLocaleTracker = TelephonyComponentFactory.getInstance().makeLocaleTracker(
                mPhone, mHandlerThread.getLooper());

        mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
        mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
        mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null);
@@ -655,6 +666,7 @@ public class ServiceStateTracker extends Handler {
        mCi.unregisterForPhysicalChannelConfiguration(this);
        mSubscriptionManager
            .removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
        mHandlerThread.quit();
        mCi.unregisterForImsNetworkStateChanged(this);
        mPhone.getCarrierActionAgent().unregisterForCarrierAction(this,
                CARRIER_ACTION_SET_RADIO_ENABLED);
@@ -2839,11 +2851,12 @@ public class ServiceStateTracker extends Handler {
            }

            tm.setNetworkOperatorNumericForPhone(mPhone.getPhoneId(), operatorNumeric);
            updateCarrierMccMncConfiguration(operatorNumeric,
                    prevOperatorNumeric, mPhone.getContext());

            if (isInvalidOperatorNumeric(operatorNumeric)) {
                if (DBG) log("operatorNumeric " + operatorNumeric + " is invalid");
                tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), "");
                // Passing empty string is important for the first update. The initial value of
                // operator numeric in locale tracker is null.
                mLocaleTracker.updateOperatorNumeric("");
                mNitzState.handleNetworkUnavailable();
            } else if (mSS.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
                // If the device is on IWLAN, modems manufacture a ServiceState with the MCC/MNC of
@@ -2855,15 +2868,8 @@ public class ServiceStateTracker extends Handler {
                    setOperatorIdd(operatorNumeric);
                }

                // Update ISO.
                String countryIsoCode = "";
                try {
                    String mcc = operatorNumeric.substring(0, 3);
                    countryIsoCode = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
                } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
                    loge("pollStateDone: countryCodeForMcc error: " + ex);
                }
                tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), countryIsoCode);
                mLocaleTracker.updateOperatorNumeric(operatorNumeric);
                String countryIsoCode = mLocaleTracker.getCurrentCountry();

                // Update Time Zone.
                boolean iccCardExists = iccCardExists();
@@ -4325,6 +4331,8 @@ public class ServiceStateTracker extends Handler {
        pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
        dumpEarfcnPairList(pw);

        mLocaleTracker.dump(fd, pw, args);

        pw.println(" Roaming Log:");
        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
        ipw.increaseIndent();
@@ -4377,15 +4385,6 @@ public class ServiceStateTracker extends Handler {
        return value;
    }

    protected void updateCarrierMccMncConfiguration(String newOp, String oldOp, Context context) {
        // if we have a change in operator, notify wifi (even to/from none)
        if (((newOp == null) && (TextUtils.isEmpty(oldOp) == false)) ||
                ((newOp != null) && (newOp.equals(oldOp) == false))) {
            log("update mccmnc=" + newOp + " fromServiceState=true");
            MccTable.updateMccMncConfiguration(context, newOp, true);
        }
    }

    /**
     * Check ISO country by MCC to see if phone is roaming in same registered country
     */
@@ -4402,8 +4401,8 @@ public class ServiceStateTracker extends Handler {
        boolean inSameCountry = true;
        final String networkMCC = operatorNumeric.substring(0, 3);
        final String homeMCC = homeNumeric.substring(0, 3);
        final String networkCountry = MccTable.countryCodeForMcc(Integer.parseInt(networkMCC));
        final String homeCountry = MccTable.countryCodeForMcc(Integer.parseInt(homeMCC));
        final String networkCountry = MccTable.countryCodeForMcc(networkMCC);
        final String homeCountry = MccTable.countryCodeForMcc(homeMCC);
        if (networkCountry.isEmpty() || homeCountry.isEmpty()) {
            // Not a valid country
            return false;
@@ -4644,4 +4643,8 @@ public class ServiceStateTracker extends Handler {
        // Return static default defined in CarrierConfigManager.
        return CarrierConfigManager.getDefaultConfig();
    }

    public LocaleTracker getLocaleTracker() {
        return mLocaleTracker;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -1793,7 +1793,7 @@ public class SubscriptionController extends ISub.Stub {
                mDefaultFallbackSubId = subId;
                // Update MCC MNC device configuration information
                String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
                MccTable.updateMccMncConfiguration(mContext, defaultMccMnc, false);
                MccTable.updateMccMncConfiguration(mContext, defaultMccMnc);

                // Broadcast an Intent for default sub change
                Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
Loading