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

Commit 9e52aefa authored by Jack Yu's avatar Jack Yu Committed by Gerrit Code Review
Browse files

Merge "Added basic network request handling"

parents db71841d 4715671c
Loading
Loading
Loading
Loading
+21 −5
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ public class DataConfigManager extends Handler {
        super(looper);
        mPhone = phone;
        mLogTag = "DCM-" + mPhone.getPhoneId();
        log("DataConfigManager created.");

        mCarrierConfigManager = mPhone.getContext().getSystemService(CarrierConfigManager.class);

@@ -96,12 +97,14 @@ public class DataConfigManager extends Handler {
        }
        mResources = SubscriptionManager.getResourcesForSubId(mPhone.getContext(),
                mPhone.getSubId());
        updateConfig();
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case EVENT_CARRIER_CONFIG_CHANGED:
                log("EVENT_CARRIER_CONFIG_CHANGED");
                updateConfig();
                break;
            default:
@@ -109,6 +112,15 @@ public class DataConfigManager extends Handler {
        }
    }

    /**
     * @return {@code true} if the configuration is carrier specific. {@code false} if the
     * configuration is the default (i.e. SIM not inserted).
     */
    public boolean isConfigCarrierSpecific() {
        return mCarrierConfig != null
                && mCarrierConfig.getBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL);
    }

    /**
     * Update the configuration.
     */
@@ -118,8 +130,11 @@ public class DataConfigManager extends Handler {
        }
        mResources = SubscriptionManager.getResourcesForSubId(mPhone.getContext(),
                mPhone.getSubId());

        updateNetworkCapabilityPriority();
        log("Data config updated.");

        log("Data config updated. Config is " + (isConfigCarrierSpecific() ? "" : "not ")
                + "carrier specific.");

        mConfigUpdateRegistrants.notifyRegistrants();
    }
@@ -128,9 +143,9 @@ public class DataConfigManager extends Handler {
     * Update the network capability priority from carrier config.
     */
    private void updateNetworkCapabilityPriority() {
        if (mCarrierConfig == null) return;
        String[] capabilityPriorityStrings = mCarrierConfig.getStringArray(
                // TODO: Add carrier config manager change
                ""/*CarrierConfigManager.KEY_NETWORK_CAPABILITY_PRIORITY_STRING_ARRAY*/);
                CarrierConfigManager.KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY);
        if (capabilityPriorityStrings != null) {
            for (String capabilityPriorityString : capabilityPriorityStrings) {
                capabilityPriorityString = capabilityPriorityString.trim().toUpperCase();
@@ -213,9 +228,10 @@ public class DataConfigManager extends Handler {
        pw.println("Network capability priority:");
        pw.increaseIndent();
        for (Map.Entry<Integer, Integer> entry : mNetworkCapabilityPriorityMap.entrySet()) {
            pw.println(DataUtils.networkCapabilityToString(entry.getKey()) + ": "
                    + entry.getValue());
            pw.print(DataUtils.networkCapabilityToString(entry.getKey()) + ":"
                    + entry.getValue() + " ");
        }
        pw.println();
        pw.decreaseIndent();
        pw.decreaseIndent();
    }
+119 −6
Original line number Diff line number Diff line
@@ -27,21 +27,24 @@ import android.net.NetworkScore;
import android.os.Looper;
import android.os.Message;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.Annotation.DataFailureCause;
import android.telephony.DataFailCause;
import android.telephony.data.DataProfile;
import android.telephony.data.DataService;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.SparseArray;

import com.android.internal.telephony.Phone;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

/**
@@ -76,6 +79,12 @@ import java.util.concurrent.atomic.AtomicInteger;
 *
 */
public class DataNetwork extends StateMachine {
    /** Event for attaching a network request. */
    private static final int EVENT_ATTACH_NETWORK_REQUEST = 1;

    /** Event for detaching a network request. */
    private static final int EVENT_DETACH_NETWORK_REQUEST = 2;

    private final @NonNull Phone mPhone;
    private final String mLogTag;
    private final LocalLog mLocalLog = new LocalLog(128);
@@ -88,6 +97,9 @@ public class DataNetwork extends StateMachine {
    private final DisconnectingState mDisconnectingState = new DisconnectingState();
    private final DisconnectedState mDisconnectedState = new DisconnectedState();

    /** The callback to receives data network state update. */
    private final @NonNull DataNetworkCallback mDataNetworkCallback;

    /** The current transport of the data network. Could be WWAN or WLAN. */
    private @TransportType int mTransport;

@@ -109,7 +121,27 @@ public class DataNetwork extends StateMachine {
    private @NonNull LinkProperties mLinkProperties;

    /** The network requests associated with this data network */
    private @NonNull List<TelephonyNetworkRequest> mAttachedNetworkRequestList = new ArrayList<>();
    private @NonNull Set<TelephonyNetworkRequest> mAttachedNetworkRequestList = new ArraySet<>();

    /** The cause for data disconnected */
    private @DataFailureCause int mFailCause = DataFailCause.NONE;

    /**
     * The interface for data network callback.
     */
    public interface DataNetworkCallback {
        /**
         * Called when data network enters {@link ConnectedState}.
         */
        void onConnected();

        /**
         * Called when data network enters {@link DisconnectedState}.
         *
         * @param cause The disconnected failure cause.
         */
        void onDisconnected(@DataFailureCause int cause);
    }

    /**
     * Constructor
@@ -120,12 +152,15 @@ public class DataNetwork extends StateMachine {
     * @param dataServiceManagers Data service managers.
     * @param transport The initial transport.
     * @param dataProfile The data profile for establishing the data network.
     * @param callback The callback to receives data network state update.
     */
    public DataNetwork(@NonNull Phone phone, @NonNull Looper looper,
            @NonNull SparseArray<DataServiceManager> dataServiceManagers,
            @TransportType int transport, @NonNull DataProfile dataProfile) {
            @TransportType int transport, @NonNull DataProfile dataProfile,
            @NonNull DataNetworkCallback callback) {
        super("DataNetwork", looper);
        mPhone = phone;
        mDataNetworkCallback = callback;
        mTransport = transport;
        mLogTag = "DN-" + sId.incrementAndGet() + "-"
                + ((transport == TRANSPORT_TYPE_WWAN) ? "C" : "I");
@@ -164,6 +199,19 @@ public class DataNetwork extends StateMachine {

        @Override
        public boolean processMessage(Message msg) {
            TelephonyNetworkRequest networkRequest = null;
            switch (msg.what) {
                case EVENT_ATTACH_NETWORK_REQUEST:
                    networkRequest = (TelephonyNetworkRequest) msg.obj;
                    mAttachedNetworkRequestList.add(networkRequest);
                    networkRequest.setAttachedNetwork(DataNetwork.this);
                    break;
                case EVENT_DETACH_NETWORK_REQUEST:
                    networkRequest = (TelephonyNetworkRequest) msg.obj;
                    mAttachedNetworkRequestList.remove(networkRequest);
                    networkRequest.setAttachedNetwork(null);
                    break;
            }
            return HANDLED;
        }
    }
@@ -198,6 +246,7 @@ public class DataNetwork extends StateMachine {
        @Override
        public void enter() {
            mNetworkAgent.markConnected();
            mDataNetworkCallback.onConnected();
        }

        @Override
@@ -206,6 +255,14 @@ public class DataNetwork extends StateMachine {

        @Override
        public boolean processMessage(Message msg) {
            TelephonyNetworkRequest networkRequest = null;
            switch (msg.what) {
                case EVENT_ATTACH_NETWORK_REQUEST:
                    networkRequest = (TelephonyNetworkRequest) msg.obj;
                    mAttachedNetworkRequestList.add(networkRequest);
                    networkRequest.setAttachedNetwork(DataNetwork.this);
                    return HANDLED;
            }
            return NOT_HANDLED;
        }
    }
@@ -260,10 +317,59 @@ public class DataNetwork extends StateMachine {
    private final class DisconnectedState extends State {
        @Override
        public void enter() {
            // Immediately discard all the unprocessed events.
            quitNow();
            // Gracefully handle all the un-processed events then quit the state machine.
            // quit() throws a QUIT event to the end of message queue. All the events before quit()
            // will be processed. Events after quit() will not be processed.
            quit();
        }
    }

    @Override
    protected void unhandledMessage(Message msg) {
        IState state = getCurrentState();
        loge("Unhandled message " + msg.what + " in state "
                + (state == null ? "null" : state.getName()));
    }

    // This is the called after all events are handled.
    @Override
    protected void onQuitting() {
            mNetworkAgent.unregister();
    }

    /**
     * Attach the network request to this data network.
     * @param networkRequest Network request to attach.
     *
     * @return {@code false} if the data network cannot be attached (i.e. not in the right state.)
     */
    public boolean attachNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
        // Check if the data network is in the right state. Note that because this check is outside
        // of the handler, it's possible that the state at this point is "connecting", but when
        // EVENT_ATTACH_NETWORK_REQUEST is actually processed, the state has entered "disconnected".
        // In that case, the request will just be throw back to the unsatisfied pool.
        if (getCurrentState() == null || getCurrentState() == mDisconnectingState
                || getCurrentState() == mDisconnectedState) {
            return false;
        }
        sendMessage(obtainMessage(EVENT_ATTACH_NETWORK_REQUEST, networkRequest));
        return true;
    }

    /**
     * Detach the network request from this data network. Note that this will not tear down the
     * network.
     * @param networkRequest Network request to detach.
     *
     * @return {@code false} if failed (i.e. The data network has disconnected. It can't process
     * more events.)
     */
    public boolean detachNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
        if (getCurrentState() == null || getCurrentState() == mDisconnectedState) {
            return false;
        }
        sendMessage(obtainMessage(EVENT_DETACH_NETWORK_REQUEST, networkRequest));
        return true;
    }

    /**
@@ -304,6 +410,13 @@ public class DataNetwork extends StateMachine {
        return null;
    }

    /**
     * @return The data profile of this data network.
     */
    public @NonNull DataProfile getDataProfile() {
        return mDataProfile;
    }

    /**
     * @return The log tag for debug message use only.
     */
+272 −7
Original line number Diff line number Diff line
@@ -18,15 +18,20 @@ package com.android.internal.telephony.data;

import android.annotation.NonNull;
import android.annotation.StringDef;
import android.net.NetworkAgent;
import android.net.NetworkRequest;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemProperties;
import android.telephony.AccessNetworkConstants;
import android.telephony.data.DataProfile;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RIL;
import com.android.telephony.Rlog;
@@ -36,6 +41,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
@@ -43,6 +49,8 @@ import java.util.List;
 * create and manage all the mobile data networks.
 */
public class DataNetworkController extends Handler {
    private final boolean VDBG;

    public static final String SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE =
            "ro.telephony.iwlan_operation_mode";

@@ -73,6 +81,23 @@ public class DataNetworkController extends Handler {
     */
    public static final String IWLAN_OPERATION_MODE_AP_ASSISTED = "AP-assisted";

    /** Event for data config updated. */
    private static final int EVENT_DATA_CONFIG_UPDATED = 1;

    /** Event for adding a network request. */
    private static final int EVENT_ADD_NETWORK_REQUEST = 2;

    /** Event for removing a network request. */
    private static final int EVENT_REMOVE_NETWORK_REQUEST = 3;

    /** Event for satisfying a single network request. */
    private static final int EVENT_SATISFY_NETWORK_REQUEST = 4;

    /** Event for setup a data network. */
    private static final int EVENT_SETUP_DATA_NETWORK = 5;



    private final Phone mPhone;
    private final String mLogTag;
    private final LocalLog mLocalLog = new LocalLog(128);
@@ -81,20 +106,90 @@ public class DataNetworkController extends Handler {
    private final @NonNull DataSettingsManager mDataSettingsManager;
    private final @NonNull DataProfileManager mDataProfileManager;
    private final @NonNull DataStallMonitor mDataStallMonitor;
    private final @NonNull DataScheduler mDataScheduler;
    private final @NonNull DataTaskManager mDataTaskManager;
    private final @NonNull SparseArray<DataServiceManager> mDataServiceManagers =
            new SparseArray<>();

    /**
     * The list of all network requests.
     */
    private final @NonNull NetworkRequestList mNetworkRequestList = new NetworkRequestList();

    /**
     * The current data network list, including the ones that are connected, connecting, or
     * disconnecting.
     */
    private final List<DataNetwork> mDataNetworkList = new ArrayList<>();
    private final @NonNull List<DataNetwork> mDataNetworkList = new ArrayList<>();

    /**
     * Contain the last 10 data networks that were connected. This is for debugging purposes only.
     */
    private final List<DataNetwork> mHistoricalDataNetworkList = new ArrayList<>();
    private final @NonNull List<DataNetwork> mHistoricalDataNetworkList = new ArrayList<>();

    /**
     * This network request list is for unsatisfied network requests. Telephony will not attempt
     * to satisfy those network requests unless there is an environmental changes, such as airplane
     * mode changes, carrier config changes, SIM state changes, RAT or registration state changes,
     * etc....
     */
    private final @NonNull NetworkRequestList mUnsatisfiedNetworkRequestList =
            new NetworkRequestList();

    /**
     * This network request list is for actively being processed network requests. Network requests
     * in this list are all scheduled with a timer that telephony will attempt to satisfy them soon.
     * Network requests will be removed from this list once they attach to a data network, or
     * telephony decides not to retry them anymore and put them into
     * {@link #mUnsatisfiedNetworkRequestList}.
     */
    private final @NonNull NetworkRequestList mActivelyProcessedNetworkRequestList =
            new NetworkRequestList();

    /**
     * The sorted network request list by priority. The highest priority network request stays at
     * the head of the list. The highest priority is 100, the lowest is 0.
     *
     * Note this list is not thread-safe. Do not access the list from different threads.
     */
    @VisibleForTesting
    public static class NetworkRequestList extends LinkedList<TelephonyNetworkRequest> {
        /**
         * Add the network request to the list. Note that the item will be inserted to the position
         * based on the priority.
         *
         * @param newRequest The network request to be added.
         * @return {@code true} if added successfully. {@code false} if the request already exists.
         */
        @Override
        public boolean add(@NonNull TelephonyNetworkRequest newRequest) {
            int index = 0;
            while (index < size()) {
                TelephonyNetworkRequest tnr = get(index);
                if (tnr.equals(newRequest)) {
                    return false;   // Do not allow duplicate
                }
                if (newRequest.getPriority() > tnr.getPriority()) {
                    break;
                }
                index++;
            }
            add(index, newRequest);
            return true;
        }

        /**
         * Dump the priority queue.
         *
         * @param pw print writer.
         */
        public void dump(IndentingPrintWriter pw) {
            pw.increaseIndent();
            for (TelephonyNetworkRequest tnr : this) {
                pw.println(tnr);
            }
            pw.decreaseIndent();
        }
    }

    /**
     * Constructor
@@ -103,10 +198,12 @@ public class DataNetworkController extends Handler {
     * @param looper The looper to be used by the handler. Currently the handler thread is the
     * phone process's main thread.
     */
    public DataNetworkController(Phone phone, Looper looper) {
    public DataNetworkController(@NonNull Phone phone, @NonNull Looper looper) {
        super(looper);
        mPhone = phone;
        mLogTag = "DNC-" + mPhone.getPhoneId();
        VDBG = Rlog.isLoggable(mLogTag, Log.VERBOSE);
        log("DataNetworkController created.");

        mDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                new DataServiceManager(mPhone, looper, AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
@@ -119,12 +216,167 @@ public class DataNetworkController extends Handler {
        mDataSettingsManager = new DataSettingsManager(mPhone, looper);
        mDataProfileManager = new DataProfileManager(mPhone, looper);
        mDataStallMonitor = new DataStallMonitor(mPhone, looper);
        mDataScheduler = new DataScheduler(mPhone, looper);
        mDataTaskManager = new DataTaskManager(mPhone, looper);

        registerAllEvents();
    }

    /**
     * Register for all events that data network controller is interested.
     */
    private void registerAllEvents() {
        mDataConfigManager.registerForConfigUpdate(this, EVENT_DATA_CONFIG_UPDATED);
    }

    @Override
    public void handleMessage(Message msg) {
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what) {
            case EVENT_DATA_CONFIG_UPDATED:
                onDataConfigUpdated();
                break;
            case EVENT_ADD_NETWORK_REQUEST:
                onAddNetworkRequest((TelephonyNetworkRequest) msg.obj);
                break;
            case EVENT_SATISFY_NETWORK_REQUEST:
                onSatisfyNetworkRequest((TelephonyNetworkRequest) msg.obj);
                break;
            case EVENT_REMOVE_NETWORK_REQUEST:
                onRemoveNetworkRequest((NetworkRequest) msg.obj);
                break;
            case EVENT_SETUP_DATA_NETWORK:
                onSetupDataNetwork((DataProfile) msg.obj);
                break;
            default:
                loge("Unexpected event " + msg.what);
        }
    }

    /**
     * Add a network request, which is originated from the apps. Note that add a network request
     * is not necessarily setting up a net {@link DataNetwork}.
     *
     * @param networkRequest Network request
     */
    public void addNetworkRequest(@NonNull NetworkRequest networkRequest) {
        // TODO: TelephonyNetworkRequest should be created in TelephonyNetworkFactory after
        //       DcTracker and other legacy data stacks are removed.
        sendMessage(obtainMessage(EVENT_ADD_NETWORK_REQUEST,
                new TelephonyNetworkRequest(networkRequest, mPhone)));
    }

    private void onAddNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
        if (!mNetworkRequestList.add(networkRequest)) {
            loge("onAddNetworkRequest: Duplicate network request. " + networkRequest);
            return;
        }
        logv("onAddNetworkRequest: added " + networkRequest);
        sendMessage(obtainMessage(EVENT_SATISFY_NETWORK_REQUEST, networkRequest));
    }

    private void onSatisfyNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
        // Check if the existing data network can satisfy this network request or not. If yes, just
        // attach the network request to the data network (even though it is connecting or
        // disconnecting).
        for (DataNetwork dataNetwork : mDataNetworkList) {
            if (networkRequest.canBeSatisfiedBy(dataNetwork.getNetworkCapabilities())) {
                if (dataNetwork.attachNetworkRequest(networkRequest)) {
                    mUnsatisfiedNetworkRequestList.remove(networkRequest);
                    mActivelyProcessedNetworkRequestList.remove(networkRequest);
                    log("Found existing data network " + dataNetwork.getLogTag() + " can satisfy"
                            + " network request " + networkRequest);
                } else {
                    log(dataNetwork.getLogTag() + " cannot be attached at this point. Put the "
                            + "network request into unsatisfied pool. " + networkRequest);
                    mUnsatisfiedNetworkRequestList.add(networkRequest);
                }
                return;
            }
        }

        // TODO: There are a more works needed to be done here.
        //   1. Check if the environment is allowed to satisfy this network request.
        //   2. Check if we can find a data profile that can satisfy this network request.

        // Can't find any way to satisfy the network request. Add it to the unsatisfied pool. We'll
        // deal with it later.
        log("Add network request to the unsatisfied list. " + networkRequest);
        mUnsatisfiedNetworkRequestList.add(networkRequest);
    }

    /**
     * Remove a network request, which is originated from the apps. Note that remove a network
     * will not result in tearing down the network. The tear down request directly comes from
     * {@link com.android.server.ConnectivityService} through
     * {@link NetworkAgent#onNetworkUnwanted()}.
     *
     * @param networkRequest Network request
     */
    // TODO: TelephonyNetworkRequest should be used after DcTracker and other legacy data stacks are
    //  removed.
    public void removeNetworkRequest(@NonNull NetworkRequest networkRequest) {
        sendMessage(obtainMessage(EVENT_REMOVE_NETWORK_REQUEST, networkRequest));
    }

    private void onRemoveNetworkRequest(@NonNull NetworkRequest networkRequest) {
        // TODO: TelephonyNetworkRequest should be used after DcTracker and other legacy data stacks
        //  are removed.
        // temp solution: find the original telephony network request.
        TelephonyNetworkRequest tnr = mNetworkRequestList.stream()
                .filter(nr -> nr.getNativeNetworkRequest().equals(networkRequest))
                .findFirst()
                .orElse(null);
        if (tnr == null) {
            loge("onRemoveNetworkRequest: Can't find original network request. "
                    + networkRequest);
            return;
        }

        if (!mNetworkRequestList.remove(tnr)) {
            loge("onRemoveNetworkRequest: Network request does not exist. " + tnr);
            return;
        }
        logv("onRemoveNetworkRequest: Removed " + tnr);

        mUnsatisfiedNetworkRequestList.remove(tnr);
        mActivelyProcessedNetworkRequestList.remove(tnr);

        for (DataNetwork dataNetwork : mDataNetworkList) {
            if (dataNetwork.detachNetworkRequest(tnr)) {
                return;
            }
        }
    }

    /**
     * Called when data config was updated.
     */
    private void onDataConfigUpdated() {
        updateNetworkRequestsPriority();
    }

    /**
     * Update each network request's priority.
     */
    private void updateNetworkRequestsPriority() {
        for (TelephonyNetworkRequest networkRequest : mNetworkRequestList) {
            networkRequest.updatePriority();
        }
    }

    /**
     * Handle setup data network event.
     *
     * @param dataProfile The data profile to setup the data network.
     */
    private void onSetupDataNetwork(@NonNull DataProfile dataProfile) {
        log("onSetupDataNetwork: dataProfile=" + dataProfile);
        for (DataNetwork dataNetwork : mDataNetworkList) {
            if (dataNetwork.getDataProfile().equals(dataProfile)) {
                log("onSetupDataNetwork: Found existing data network " + dataNetwork.getLogTag()
                        + " has the same data profile.");
                return;
            }
        }
    }

    /**
@@ -170,6 +422,14 @@ public class DataNetworkController extends Handler {
        Rlog.e(mLogTag, s);
    }

    /**
     * Log verbose messages.
     * @param s debug messages.
     */
    private void logv(@NonNull String s) {
        if (VDBG) Rlog.v(mLogTag, s);
    }

    /**
     * Log debug messages and also log into the local log.
     * @param s debug messages
@@ -207,6 +467,11 @@ public class DataNetworkController extends Handler {
            }
        }
        pw.decreaseIndent();
        pw.println("Unsatisfied network requests in priority order.");
        mUnsatisfiedNetworkRequestList.dump(pw);
        pw.println("Actively processed network requests in priority order.");
        mActivelyProcessedNetworkRequestList.dump(pw);


        pw.println("Local logs:");
        pw.increaseIndent();
@@ -216,7 +481,7 @@ public class DataNetworkController extends Handler {
        pw.println("-------------------------------------");
        mDataProfileManager.dump(fd, pw, args);
        pw.println("-------------------------------------");
        mDataScheduler.dump(fd, pw, args);
        mDataTaskManager.dump(fd, pw, args);
        pw.println("-------------------------------------");
        mDataSettingsManager.dump(fd, pw, args);
        pw.println("-------------------------------------");
+12 −12

File changed.

Preview size limit exceeded, changes collapsed.

+183 −0

File changed and moved.

Preview size limit exceeded, changes collapsed.

Loading