content: /*
   * Copyright (C) 2011 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 android.net;
  
  import static android.net.ConnectivityManager.TYPE_WIFI;
  import static android.net.ConnectivityManager.getNetworkTypeName;
  import static android.net.ConnectivityManager.isNetworkTypeMobile;
  
  import android.content.Context;
  import android.net.wifi.WifiInfo;
  import android.net.wifi.WifiManager;
  import android.os.Build;
  import android.service.NetworkIdentityProto;
  import android.telephony.TelephonyManager;
  import android.util.Slog;
  import android.util.proto.ProtoOutputStream;
  
  import java.util.Objects;
  
  /**
   * Network definition that includes strong identity. Analogous to combining
   * {@link NetworkInfo} and an IMSI.
   *
   * @hide
   */
  public class NetworkIdentity implements Comparable<NetworkIdentity> {
      private static final String TAG = "NetworkIdentity";
  
      /**
       * When enabled, combine all {@link #mSubType} together under
       * {@link #SUBTYPE_COMBINED}.
       *
       * @deprecated we no longer offer to collect statistics on a per-subtype
       *             basis; this is always disabled.
       */
      @Deprecated
      public static final boolean COMBINE_SUBTYPE_ENABLED = true;
  
      public static final int SUBTYPE_COMBINED = -1;
  
      final int mType;
      final int mSubType;
      final String mSubscriberId;
      final String mNetworkId;
      final boolean mRoaming;
      final boolean mMetered;
      final boolean mDefaultNetwork;
  
      public NetworkIdentity(
              int type, int subType, String subscriberId, String networkId, boolean roaming,
              boolean metered, boolean defaultNetwork) {
          mType = type;
          mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType;
          mSubscriberId = subscriberId;
          mNetworkId = networkId;
          mRoaming = roaming;
          mMetered = metered;
          mDefaultNetwork = defaultNetwork;
      }
  
      @Override
      public int hashCode() {
          return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered,
                  mDefaultNetwork);
      }
  
      @Override
      public boolean equals(Object obj) {
          if (obj instanceof NetworkIdentity) {
              final NetworkIdentity ident = (NetworkIdentity) obj;
              return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
                      && Objects.equals(mSubscriberId, ident.mSubscriberId)
                      && Objects.equals(mNetworkId, ident.mNetworkId)
                      && mMetered == ident.mMetered
                      && mDefaultNetwork == ident.mDefaultNetwork;
          }
          return false;
      }
  
      @Override
      public String toString() {
          final StringBuilder builder = new StringBuilder("{");
          builder.append("type=").append(getNetworkTypeName(mType));
          builder.append(", subType=");
          if (COMBINE_SUBTYPE_ENABLED) {
              builder.append("COMBINED");
          } else if (ConnectivityManager.isNetworkTypeMobile(mType)) {
              builder.append(TelephonyManager.getNetworkTypeName(mSubType));
          } else {
              builder.append(mSubType);
          }
          if (mSubscriberId != null) {
              builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
          }
          if (mNetworkId != null) {
              builder.append(", networkId=").append(mNetworkId);
          }
          if (mRoaming) {
              builder.append(", ROAMING");
          }
          builder.append(", metered=").append(mMetered);
          builder.append(", defaultNetwork=").append(mDefaultNetwork);
          return builder.append("}").toString();
      }
  
      public void writeToProto(ProtoOutputStream proto, long tag) {
          final long start = proto.start(tag);
  
          proto.write(NetworkIdentityProto.TYPE, mType);
  
          // Not dumping mSubType, subtypes are no longer supported.
  
          if (mSubscriberId != null) {
              proto.write(NetworkIdentityProto.SUBSCRIBER_ID, scrubSubscriberId(mSubscriberId));
          }
          proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId);
          proto.write(NetworkIdentityProto.ROAMING, mRoaming);
          proto.write(NetworkIdentityProto.METERED, mMetered);
          proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork);
  
          proto.end(start);
      }
  
      public int getType() {
          return mType;
      }
  
      public int getSubType() {
          return mSubType;
      }
  
      public String getSubscriberId() {
          return mSubscriberId;
      }
  
      public String getNetworkId() {
          return mNetworkId;
      }
  
      public boolean getRoaming() {
          return mRoaming;
      }
  
      public boolean getMetered() {
          return mMetered;
      }
  
      public boolean getDefaultNetwork() {
          return mDefaultNetwork;
      }
  
      /**
       * Scrub given IMSI on production builds.
       */
      public static String scrubSubscriberId(String subscriberId) {
          if (Build.IS_ENG) {
              return subscriberId;
          } else if (subscriberId != null) {
              // TODO: parse this as MCC+MNC instead of hard-coding
              return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
          } else {
              return "null";
          }
      }
  
      /**
       * Scrub given IMSI on production builds.
       */
      public static String[] scrubSubscriberId(String[] subscriberId) {
          if (subscriberId == null) return null;
          final String[] res = new String[subscriberId.length];
          for (int i = 0; i < res.length; i++) {
              res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
          }
          return res;
      }
  
      /**
       * Build a {@link NetworkIdentity} from the given {@link NetworkState},
       * assuming that any mobile networks are using the current IMSI.
       */
      public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state,
              boolean defaultNetwork) {
          final int type = state.networkInfo.getType();
          final int subType = state.networkInfo.getSubtype();
  
          String subscriberId = null;
          String networkId = null;
          boolean roaming = !state.networkCapabilities.hasCapability(
                  NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
          boolean metered = !state.networkCapabilities.hasCapability(
                  NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
  
          if (isNetworkTypeMobile(type)) {
              if (state.subscriberId == null) {
                  if (state.networkInfo.getState() != NetworkInfo.State.DISCONNECTED &&
                          state.networkInfo.getState() != NetworkInfo.State.UNKNOWN) {
                      Slog.w(TAG, "Active mobile network without subscriber! ni = "
                              + state.networkInfo);
                  }
              }
  
              subscriberId = state.subscriberId;
  
          } else if (type == TYPE_WIFI) {
              if (state.networkId != null) {
                  networkId = state.networkId;
              } else {
                  final WifiManager wifi = (WifiManager) context.getSystemService(
                          Context.WIFI_SERVICE);
                  final WifiInfo info = wifi.getConnectionInfo();
                  networkId = info != null ? info.getSSID() : null;
              }
          }
  
          return new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered,
                  defaultNetwork);
      }
  
      @Override
      public int compareTo(NetworkIdentity another) {
          int res = Integer.compare(mType, another.mType);
          if (res == 0) {
              res = Integer.compare(mSubType, another.mSubType);
          }
          if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
              res = mSubscriberId.compareTo(another.mSubscriberId);
          }
          if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
              res = mNetworkId.compareTo(another.mNetworkId);
          }
          if (res == 0) {
              res = Boolean.compare(mRoaming, another.mRoaming);
          }
          if (res == 0) {
              res = Boolean.compare(mMetered, another.mMetered);
          }
          if (res == 0) {
              res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork);
          }
          return res;
      }
  }
  