AlternativeNetworkAccessService

AlternativeNetworkAccessService: phase 1 captures
Service class of AlternativeNetworkAccessService
Assumes only one opportunistic data profile is available
Assumes profile is already activated

Bug: 113106744
Test: Integration Test with CBRS network
Change-Id: I02ca1c5ea62937d41e0b69f569ea336665db2822
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index b1e061f..ba36f1b 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -62,6 +62,7 @@
 import com.android.internal.telephony.dataconnection.DataConnectionReasons;
 import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.anas.AlternativeNetworkAccessService;
 import com.android.phone.common.CallLogAsync;
 import com.android.phone.settings.SettingsConstants;
 import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
@@ -333,6 +334,7 @@
             phoneMgr = PhoneInterfaceManager.init(this, PhoneFactory.getDefaultPhone());
 
             configLoader = CarrierConfigLoader.init(this);
+            AlternativeNetworkAccessService.initInstance(this);
 
             // Create the CallNotifier singleton, which handles
             // asynchronous events from the telephony layer (like
diff --git a/src/com/android/phone/anas/AlternativeNetworkAccessService.java b/src/com/android/phone/anas/AlternativeNetworkAccessService.java
new file mode 100644
index 0000000..b972813
--- /dev/null
+++ b/src/com/android/phone/anas/AlternativeNetworkAccessService.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 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.phone.anas;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Binder;
+import android.os.ServiceManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IAnas;
+import com.android.internal.telephony.TelephonyPermissions;
+
+/**
+ * AlternativeNetworkAccessService implements ianas.
+ * It scans network and matches the results with opportunistic subscriptions.
+ * Use the same to provide user opportunistic data in areas with corresponding networks
+ */
+public class AlternativeNetworkAccessService extends IAnas.Stub {
+    private Context mContext;
+    private TelephonyManager mTelephonyManager;
+    private SubscriptionManager mSubsriptionManager;
+
+    private final Object mLock = new Object();
+    private boolean mIsEnabled;
+    private ANASProfileSelector mProfileSelector;
+    private ANASServiceStateEvaluator mServiceStateEvaluator;
+    private SharedPreferences mSharedPref;
+
+    /** The singleton instance. */
+    private static AlternativeNetworkAccessService sInstance = null;
+    private static final String TAG = "ANAS";
+    private static final String PREF_NAME = TAG;
+    private static final String PREF_ENABLED = "isEnabled";
+    private static final boolean DBG = true;
+
+    /**
+     * Profile selection callback. Will be called once Profile selector decides on
+     * the opportunistic data profile.
+     */
+    private ANASProfileSelector.ANASProfileSelectionCallback  mProfileSelectionCallback =
+            new ANASProfileSelector.ANASProfileSelectionCallback() {
+
+                @Override
+                public void onProfileSelectionDone(int dataSubId, int voiceSubId) {
+                    logDebug("profile selection done");
+                    mProfileSelector.stopProfileSelection();
+                    mServiceStateEvaluator.startEvaluation(dataSubId, voiceSubId);
+                }
+            };
+
+    /**
+     * Service state evaluator callback. Will be called once service state evaluator thinks
+     * that current opportunistic data is not providing good service.
+     */
+    private ANASServiceStateEvaluator.ANASServiceEvaluatorCallback mServiceEvaluatorCallback =
+            new ANASServiceStateEvaluator.ANASServiceEvaluatorCallback() {
+                @Override
+                public void onBadDataService() {
+                    logDebug("Bad opportunistic data service");
+                    mServiceStateEvaluator.stopEvaluation();
+                    mProfileSelector.selectPrimaryProfileForData();
+                    mProfileSelector.startProfileSelection();
+                }
+            };
+
+    /**
+     * create AlternativeNetworkAccessService instance
+     *
+     * @param c context
+     *
+     */
+    public static void initInstance(Context c) {
+        if (sInstance == null) {
+            sInstance = new AlternativeNetworkAccessService(c);
+        }
+        return;
+    }
+
+    /**
+     * get AlternativeNetworkAccessService instance
+     *
+     */
+    @VisibleForTesting
+    public static AlternativeNetworkAccessService getInstance() {
+        if (sInstance == null) {
+            Log.wtf(TAG, "getInstance null");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Enable or disable Alternative Network Access service.
+     *
+     * This method should be called to enable or disable
+     * AlternativeNetworkAccess service on the device.
+     *
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *
+     * @param enable enable(True) or disable(False)
+     * @param callingPackage caller's package name
+     * @return returns true if successfully set.
+     */
+    @Override
+    public boolean setEnable(boolean enable, String callingPackage) {
+        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+                mContext, mSubsriptionManager.getDefaultSubscriptionId(), "setEnable");
+        log("setEnable: " + enable);
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            enableAlternativeNetworkAccess(enable);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        return true;
+    }
+
+    /**
+     * is Alternative Network Access service enabled
+     *
+     * This method should be called to determine if the Alternative Network Access service
+     * is enabled
+     *
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *
+     * @param callingPackage caller's package name
+     */
+    @Override
+    public boolean isEnabled(String callingPackage) {
+        TelephonyPermissions.enforeceCallingOrSelfReadPhoneStatePermissionOrCarrierPrivilege(
+                mContext, mSubsriptionManager.getDefaultSubscriptionId(), "isEnabled");
+        return mIsEnabled;
+    }
+
+    /**
+     * initialize ANAS and register as service.
+     * Read persistent state to update enable state
+     * Start sub components if already enabled.
+     * @param context context instance
+     */
+    private void initializeAndRegisterAsService(Context context) {
+        mContext = context;
+        mTelephonyManager = TelephonyManager.from(mContext);
+        mServiceStateEvaluator = new ANASServiceStateEvaluator(mContext, mServiceEvaluatorCallback);
+        mProfileSelector = new ANASProfileSelector(mContext, mProfileSelectionCallback);
+        mSharedPref = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+        mSubsriptionManager = (SubscriptionManager) mContext.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+
+        /* register the service */
+        if (ServiceManager.getService("ianas") == null) {
+            ServiceManager.addService("ianas", this);
+        }
+
+        enableAlternativeNetworkAccess(getPersistentEnableState());
+    }
+
+    private AlternativeNetworkAccessService(Context c) {
+        initializeAndRegisterAsService(c);
+        log("init completed");
+    }
+
+    private boolean getPersistentEnableState() {
+        return mSharedPref.getBoolean(PREF_ENABLED, true);
+    }
+
+    private void updateEnableState(boolean enable) {
+        mIsEnabled = enable;
+        mSharedPref.edit().putBoolean(PREF_ENABLED, mIsEnabled).apply();
+    }
+
+    /**
+     * update the enable state
+     * start profile selection if enabled.
+     * @param enable enable(true) or disable(false)
+     */
+    private void enableAlternativeNetworkAccess(boolean enable) {
+        synchronized (mLock) {
+            if (mIsEnabled != enable) {
+                updateEnableState(enable);
+                if (mIsEnabled) {
+                    mProfileSelector.startProfileSelection();
+                } else {
+                    mProfileSelector.stopProfileSelection();
+                }
+            }
+        }
+        logDebug("service is enable state " + mIsEnabled);
+    }
+
+    private void log(String msg) {
+        Rlog.d(TAG, msg);
+    }
+
+    private void logDebug(String msg) {
+        if (DBG) Rlog.d(TAG, msg);
+    }
+}