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

Commit b0fec2e4 authored by Youngtae Cha's avatar Youngtae Cha Committed by Android (Google) Code Review
Browse files

Merge "Support config update for satellite" into main

parents f30c091f af1ec68e
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line

// Copyright 2024 Google LLC
//
// 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.

syntax = "proto2";
package com.android.internal.telephony.satellite;

option java_package = "com.android.internal.telephony.satellite";
option java_outer_classname = "SatelliteConfigData";

message TelephonyConfigProto {
  optional SatelliteConfigProto satellite = 1;
}

message SatelliteConfigProto {
  optional int32 version = 1;
  repeated CarrierSupportedSatelliteServicesProto carrier_supported_satellite_services = 2;
  optional SatelliteRegionProto device_satellite_region = 3;
}

message CarrierSupportedSatelliteServicesProto {
  optional int32 carrier_id = 1;
  repeated SatelliteProviderCapabilityProto supported_satellite_provider_capabilities = 2;
}

message SatelliteProviderCapabilityProto{
  optional string carrier_plmn = 1;
  repeated int32 allowed_services = 2;
}

message SatelliteRegionProto {
  optional bytes s2_cell_file = 1;
  repeated string country_codes = 2;
  optional bool is_allowed = 3;
}
 No newline at end of file
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.configupdate;

import android.annotation.NonNull;
import android.annotation.Nullable;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public abstract class ConfigParser<T> {

    public static final int VERSION_UNKNOWN = -1;

    protected int mVersion = VERSION_UNKNOWN;

    protected T mConfig;

    /**
     * Constructs a parser from the raw data
     *
     * @param data the config data
     */
    public ConfigParser(@Nullable byte[] data) {
        parseData(data);
    }

    /**
     * Constructs a parser from the input stream
     *
     * @param input the input stream of the config
     * @return the instance of the ConfigParser
     */
    public ConfigParser(@NonNull InputStream input) throws IOException {
        parseData(input.readAllBytes());
    }

    /**
     * Constructs a parser from the input stream
     *
     * @param file the input file of the config
     * @return the instance of the ConfigParser
     */
    public ConfigParser(@NonNull File file) throws IOException {
        try (FileInputStream fis = new FileInputStream(file)) {
            parseData(fis.readAllBytes());
        }
    }

    /**
     * Get the version of the config
     *
     * @return the version of the config if it is defined, or VERSION_UNKNOWN
     */
    public int getVersion() {
        return mVersion;
    }

    /**
     * Get the config
     *
     * @return the config
     */
    public @Nullable T getConfig() {
        return mConfig;
    }

    /**
     * Get the sub config raw data by id
     *
     * @param id the identifier of the sub config
     * @return the raw data of the sub config
     */
    public @Nullable byte[] getData(String id) {
        return null;
    }

    /**
     * Parse the config data
     *
     * @param data the config data
     */
    protected abstract void parseData(@Nullable byte[] data);
}
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.configupdate;

import android.annotation.NonNull;
import android.annotation.Nullable;

import java.util.concurrent.Executor;

public interface ConfigProviderAdaptor {

    String DOMAIN_SATELLITE = "satellite";

    /**
     * Get the config from the provider
     */
    @Nullable ConfigParser getConfigParser(String domain);

    class Callback {
        /**
         * The callback when the config is changed
         * @param config the config is changed
         */
        public void onChanged(@Nullable ConfigParser config) {}
    }

    /**
     * Register the callback to monitor the config change
     * @param executor The executor to execute the callback.
     * @param callback the callback to monitor the config change
     */
    void registerCallback(@NonNull Executor executor, @NonNull Callback callback);

    /**
     * Unregister the callback
     * @param callback the callback to be unregistered
     */
    void unregisterCallback(@NonNull Callback callback);
}
+200 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.configupdate;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.satellite.SatelliteConfigParser;
import com.android.server.updates.ConfigUpdateInstallReceiver;

import libcore.io.IoUtils;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;

public class TelephonyConfigUpdateInstallReceiver extends ConfigUpdateInstallReceiver implements
        ConfigProviderAdaptor {

    private static final String TAG = "TelephonyConfigUpdateInstallReceiver";
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    protected static final String UPDATE_DIR = "/data/misc/telephonyconfig";
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    protected static final String UPDATE_CONTENT_PATH = "telephony_config.pb";
    protected static final String UPDATE_METADATA_PATH = "metadata/";
    public static final String VERSION = "version";

    private ConcurrentHashMap<Executor, Callback> mCallbackHashMap = new ConcurrentHashMap<>();
    @NonNull
    private final Object mConfigParserLock = new Object();
    @GuardedBy("mConfigParserLock")
    private ConfigParser mConfigParser;


    public static TelephonyConfigUpdateInstallReceiver sReceiverAdaptorInstance =
            new TelephonyConfigUpdateInstallReceiver();

    /**
     * @return The singleton instance of TelephonyConfigUpdateInstallReceiver
     */
    @NonNull
    public static TelephonyConfigUpdateInstallReceiver getInstance() {
        return sReceiverAdaptorInstance;
    }

    public TelephonyConfigUpdateInstallReceiver() {
        super(UPDATE_DIR, UPDATE_CONTENT_PATH, UPDATE_METADATA_PATH, VERSION);
    }

    /**
     * @return byte array type of config data protobuffer file
     */
    @Nullable
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public byte[] getCurrentContent() {
        try {
            return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath());
        } catch (IOException e) {
            Slog.i(TAG, "Failed to read current content, assuming first update!");
            return null;
        }
    }

    @Override
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
    public void postInstall(Context context, Intent intent) {
        Log.d(TAG, "Telephony config is updated in file partition");
        ConfigParser updatedConfigParser = getNewConfigParser(DOMAIN_SATELLITE,
                getCurrentContent());

        if (updatedConfigParser == null) {
            Log.d(TAG, "updatedConfigParser is null");
            return;
        }

        boolean isParserChanged = false;

        synchronized (getInstance().mConfigParserLock) {
            if (getInstance().mConfigParser == null) {
                getInstance().mConfigParser = updatedConfigParser;
                isParserChanged = true;
            } else {
                int updatedVersion = updatedConfigParser.mVersion;
                int previousVersion = getInstance().mConfigParser.mVersion;
                Log.d(TAG, "previous version is " + previousVersion + " | updated version is "
                        + updatedVersion);
                if (updatedVersion > previousVersion) {
                    getInstance().mConfigParser = updatedConfigParser;
                    isParserChanged = true;
                }
            }
        }

        if (isParserChanged) {
            if (getInstance().mCallbackHashMap.keySet().isEmpty()) {
                Log.d(TAG, "mCallbackHashMap.keySet().isEmpty");
                return;
            }
            Iterator<Executor> iterator =
                    getInstance().mCallbackHashMap.keySet().iterator();
            while (iterator.hasNext()) {
                Executor executor = iterator.next();
                getInstance().mCallbackHashMap.get(executor).onChanged(
                        updatedConfigParser);
            }
        }
    }

    @Nullable
    @Override
    public ConfigParser getConfigParser(String domain) {
        Log.d(TAG, "getConfigParser");
        synchronized (getInstance().mConfigParserLock) {
            if (getInstance().mConfigParser == null) {
                Log.d(TAG, "CreateNewConfigParser with domain " + domain);
                getInstance().mConfigParser = getNewConfigParser(domain, getCurrentContent());
            }
            return getInstance().mConfigParser;
        }
    }

    @Override
    public void registerCallback(@NonNull Executor executor, @NonNull Callback callback) {
        mCallbackHashMap.put(executor, callback);
    }

    @Override
    public void unregisterCallback(@NonNull Callback callback) {
        Iterator<Executor> iterator = mCallbackHashMap.keySet().iterator();
        while (iterator.hasNext()) {
            Executor executor = iterator.next();
            if (mCallbackHashMap.get(executor) == callback) {
                mCallbackHashMap.remove(executor);
                break;
            }
        }
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public File getUpdateDir() {
        return getInstance().updateDir;
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public File getUpdateContent() {
        return getInstance().updateContent;
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public ConcurrentHashMap<Executor, Callback> getCallbackMap() {
        return getInstance().mCallbackHashMap;
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public void setCallbackMap(ConcurrentHashMap<Executor, Callback> map) {
        getInstance().mCallbackHashMap = map;
    }

    /**
     * @param data byte array type of config data
     * @return when data is null, return null otherwise return ConfigParser
     */
    @Nullable
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public ConfigParser getNewConfigParser(String domain, @Nullable byte[] data) {
        if (data == null) {
            Log.d(TAG, "content data is null");
            return null;
        }
        switch (domain) {
            case DOMAIN_SATELLITE:
                return new SatelliteConfigParser(data);
            default:
                Log.e(TAG, "DOMAIN should be specified");
                return null;
        }
    }
}
+233 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.satellite;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.satellite.nano.SatelliteConfigData;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * SatelliteConfig is utility class for satellite.
 * It is obtained through the getConfig() at the SatelliteConfigParser.
 */
public class SatelliteConfig {

    private static final String TAG = "SatelliteConfig";
    private static final String SATELLITE_DIR_NAME = "satellite";
    private static final String S2_CELL_FILE_NAME = "s2_cell_file";
    private int mVersion;
    private Map<Integer, Map<String, Set<Integer>>> mSupportedServicesPerCarrier;
    private List<String> mSatelliteRegionCountryCodes;
    private Boolean mIsSatelliteRegionAllowed;
    private Path mSatS2FilePath;
    private SatelliteConfigData.SatelliteConfigProto mConfigData;

    public SatelliteConfig(SatelliteConfigData.SatelliteConfigProto configData) {
        mConfigData = configData;
        mVersion = mConfigData.version;
        mSupportedServicesPerCarrier = getCarrierSupportedSatelliteServices();
        mSatelliteRegionCountryCodes = List.of(
                mConfigData.deviceSatelliteRegion.countryCodes);
        mIsSatelliteRegionAllowed = mConfigData.deviceSatelliteRegion.isAllowed;
        mSatS2FilePath = null;

        Log.d(TAG, "mVersion:" + mVersion + " | "
                + "mSupportedServicesPerCarrier:" + mSupportedServicesPerCarrier + " | "
                + "mSatelliteRegionCountryCodes:" + mSatelliteRegionCountryCodes + " | "
                + "mIsSatelliteRegionAllowed:" + mIsSatelliteRegionAllowed + " | "
                + "s2CellFile size:" + mConfigData.deviceSatelliteRegion.s2CellFile.length);
    }

    /**
     * @return a Map data with carrier_id, plmns and allowed_services.
     */
    private Map<Integer, Map<String, Set<Integer>>> getCarrierSupportedSatelliteServices() {
        SatelliteConfigData.CarrierSupportedSatelliteServicesProto[] satelliteServices =
                mConfigData.carrierSupportedSatelliteServices;
        Map<Integer, Map<String, Set<Integer>>> carrierToServicesMap = new HashMap<>();
        for (SatelliteConfigData.CarrierSupportedSatelliteServicesProto carrierProto :
                satelliteServices) {
            SatelliteConfigData.SatelliteProviderCapabilityProto[] satelliteCapabilities =
                    carrierProto.supportedSatelliteProviderCapabilities;
            Map<String, Set<Integer>> satelliteCapabilityMap = new HashMap<>();
            for (SatelliteConfigData.SatelliteProviderCapabilityProto capabilityProto :
                    satelliteCapabilities) {
                String carrierPlmn = capabilityProto.carrierPlmn;
                Set<Integer> allowedServices = new HashSet<>();
                for (int service : capabilityProto.allowedServices) {
                    allowedServices.add(service);
                }
                satelliteCapabilityMap.put(carrierPlmn, allowedServices);
            }
            carrierToServicesMap.put(carrierProto.carrierId, satelliteCapabilityMap);
        }
        return carrierToServicesMap;
    }

    /**
     * Get satellite plmns for carrier
     *
     * @param carrierId the carrier identifier.
     * @return Plmns corresponding to carrier identifier.
     */
    @NonNull
    public List<String> getAllSatellitePlmnsForCarrier(int carrierId) {
        if (mSupportedServicesPerCarrier != null) {
            Map<String, Set<Integer>> satelliteCapabilitiesMap = mSupportedServicesPerCarrier.get(
                    carrierId);
            if (satelliteCapabilitiesMap != null) {
                return new ArrayList<>(satelliteCapabilitiesMap.keySet());
            }
        }
        Log.d(TAG, "getAllSatellitePlmnsForCarrier : mConfigData is null or no config data");
        return new ArrayList<>();
    }

    /**
     * Get supported satellite services of all providers for a carrier.
     * The format of the return value - Key: PLMN, Value: Set of supported satellite services.
     *
     * @param carrierId the carrier identifier.
     * @return all supported satellite services for a carrier
     */
    @NonNull
    public Map<String, Set<Integer>> getSupportedSatelliteServices(int carrierId) {
        if (mSupportedServicesPerCarrier != null) {
            Map<String, Set<Integer>> satelliteCapaMap =
                    mSupportedServicesPerCarrier.get(carrierId);
            if (satelliteCapaMap != null) {
                return satelliteCapaMap;
            } else {
                Log.d(TAG, "No supported services found for carrier=" + carrierId);
            }
        } else {
            Log.d(TAG, "mSupportedServicesPerCarrier is null");
        }
        return new HashMap<>();
    }

    /**
     * @return satellite region country codes
     */
    @NonNull
    public List<String> getDeviceSatelliteCountryCodes() {
        if (mSatelliteRegionCountryCodes != null) {
            return mSatelliteRegionCountryCodes;
        }
        Log.d(TAG, "getDeviceSatelliteCountryCodes : mConfigData is null or no config data");
        return new ArrayList<>();
    }

    /**
     * @return satellite access allow value, if there is no config data then it returns null.
     */
    @Nullable
    public Boolean isSatelliteDataForAllowedRegion() {
        if (mIsSatelliteRegionAllowed == null) {
            Log.d(TAG, "getIsSatelliteRegionAllowed : mConfigData is null or no config data");
        }
        return mIsSatelliteRegionAllowed;
    }


    /**
     * @param context the Context
     * @return satellite s2_cell_file path
     */
    @Nullable
    public Path getSatelliteS2CellFile(@Nullable Context context) {
        if (context == null) {
            Log.d(TAG, "getSatelliteS2CellFile : context is null");
            return null;
        }

        if (isFileExist(mSatS2FilePath)) {
            Log.d(TAG, "File mSatS2FilePath is already exist");
            return mSatS2FilePath;
        }

        if (mConfigData != null && mConfigData.deviceSatelliteRegion != null) {
            mSatS2FilePath = copySatS2FileToPhoneDirectory(context,
                    mConfigData.deviceSatelliteRegion.s2CellFile);
            return mSatS2FilePath;
        }
        Log.d(TAG, "getSatelliteS2CellFile :"
                + "mConfigData is null or mConfigData.deviceSatelliteRegion is null");
        return null;
    }

    /**
     * @param context       the Context
     * @param byteArrayFile byte array type of protobuffer config data
     * @return the satellite_cell_file path
     */
    @Nullable
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public Path copySatS2FileToPhoneDirectory(@Nullable Context context,
            @Nullable byte[] byteArrayFile) {

        if (context == null || byteArrayFile == null) {
            Log.d(TAG, "copySatS2FileToPhoneDirectory : context or byteArrayFile are null");
            return null;
        }

        File satS2FileDir = context.getDir(SATELLITE_DIR_NAME, Context.MODE_PRIVATE);
        if (!satS2FileDir.exists()) {
            satS2FileDir.mkdirs();
        }

        Path targetSatS2FilePath = satS2FileDir.toPath().resolve(S2_CELL_FILE_NAME);
        try {
            InputStream inputStream = new ByteArrayInputStream(byteArrayFile);
            if (inputStream == null) {
                Log.d(TAG, "copySatS2FileToPhoneDirectory: Resource=" + S2_CELL_FILE_NAME
                        + " not found");
            } else {
                Files.copy(inputStream, targetSatS2FilePath, StandardCopyOption.REPLACE_EXISTING);
            }
        } catch (IOException ex) {
            Log.e(TAG, "copySatS2FileToPhoneDirectory: ex=" + ex);
        }
        return targetSatS2FilePath;
    }

    /**
     * @return {@code true} if the SatS2File is already existed and {@code false} otherwise.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public boolean isFileExist(Path filePath) {
        return Files.exists(filePath);
    }
}
Loading