android 代码更新有感:
1. Android O的Settings引入了PreferenceController这个包装类来实现对Preference的精细化控制,让代码结构更加地鲜明,很好地体现了单一职责原则。
1. Android 名企面试题及涉及知识点整理 https://www.jianshu.com/p/735be5ece9e8
1. Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。final类不能被继承,没有子类,final类中的方法默认是final的。 final方法不能被子类的方法覆盖,但可以被继承。 final成员变量表示常量,只能被赋值一次,赋值后值不再改变。 final不能用于修饰构造方法。 注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
final修饰的类有什么特点 https://www.cnblogs.com/huiyuantang/p/5422317.html
抽象类与接口紧密相关,它们不能实例化,并且常常部分实现或根本不实现。抽象类和接口之间的一个主要差别是:类可以实现无限个接口,但仅能从一个抽象(或任何其他类型)类继承。
1. java 中为什么要把一个类的构造函数protect? 使用了protected, 客户程序(不在同一个包内)就不能随便创建该类的一个实例。 但是由于是protected, 使得该类可以被继承。 如果构造函数是私有的。 那么该类无法被继承(因为子类构造时要调用超类的构造函数,而private 使得子类无法调用),无法从外界获得一个对象。 但是可以在类的内部产生一个实例的。 例如singleton就是使用private的构造函数, 然后在内部维护一个实例。 而提供一个static的getInstance方法来获取这个实例。 如果构造函数是protected,那么该类可以继承,可以在被包内其他类中产生实例,但是无法再包外或者子类以外的地方产生实例。
这个同时和public protected private 的属性有关,bublic全部都可以访问 protected 类内部,同一个包或者同一个目录 private 只有类的内部
1. Java的接口总结 https://blog.youkuaiyun.com/weixin_38503885/article/details/96565296
1. @notnull & @nullable: 如果可以传入NULL值,则标记为@Nullable,如果不可以,则标注为@Nonnull。
@Override是伪代码,所以是可写可不写的.它表示方法重写,写上会给我们带来好处.
1.可以当注释用,方便阅读.
2.告诉阅读你代码的人,这是方法的复写.
3.编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错.
1 String.format("%s:%s", scanResult.SSID, scanResult.BSSID);
1. android-Service和Thread的区别
1.服务不是单一的进程。服务没有自己的进程,应用程序可以不同,服务运行在相同的进程中。
2.服务不是线程。可以在线程中工作。
一.在应用中,如果是长时间的在后台运行,而且不需要交互的情况下,使用服务。同样是在后台运行,不需要交互的情况下,如果只是完成某个任务,之后就不需要运行,而且可能是多个任务,需需要长时间运行的情况下使用线程。
二.如果任务占用CPU时间多,资源大的情况下,要使用线程。
servie是系统的组件,它由系统进程托管(servicemanager);它们之间的通信类似于client和server,是一种轻量级的ipc通信,这种通信的载体是binder,它是在linux层交换信息的一种ipc。而thread是由本应用程序托管。
1). Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。
2). Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。
既然这样,那么我们为什么要用 Service 呢?其实这跟 android 的系统机制有关,我们先拿Thread 来说。Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。
举个例子:如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。
因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。
Thread & Runnable 的应用实例:
Thread & Runnable 没有可比性。Thread 实现了Runnable的接口并进行里了扩展,Runnable是一个接口类。实现并启动线程有两种方法:1、写一个类继承自Thread类,重写run方法。用start方法启动线程 2、写一个类实现Runnable接口,实现run方法。用new Thread(Runnable target).start()方法来启动.
Thread thread = new Thread() {
@Override public void run() {
reportMemUsage(memInfos);
}
};
thread.start();
new Thread(new Runnable() {
@Override
public void run() {
}
}
}).start();
Thread 提供了多种构造方法,最后都是通过init()来创建的,init方法共有4个参数,分别代表:ThreadGroup g 指定当前线程的线程组;Runnable target 指定运行其中的Runnable,一般都需要指定,不指定的线程没有意义,或者可以通过创建Thread的子类并重新run方法;String name 线程的名称,不指定自动生成;long stackSize 预期堆栈大小,不指定默认为0
实现Runnable接口相对于继承Thread类的优点:适合多个相同程序代码的线程去处理同一个资源;可以避免由于Java的单继承性(只能继承一个父类)带来的局限性;增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。https://blog.youkuaiyun.com/h369262674/article/details/51771520
参见:JAVA线程之Thread类详解: https://blog.youkuaiyun.com/pengqiaowolf/article/details/80442071
1. Android开发中必备的代码Review清单 https://www.jianshu.com/p/f49652087970
1. Lambda表达式
private void runAndLog(Runnable r, String verboseLog) {
ThreadUtils.postOnMainThread(() -> { //() -> 这种表达方式是一个Runnable的匿名对象,大括号里是Runnable要实现的方法。
r.run();
});
}
2. 线程能嵌套吗?如果嵌套怎么理解它呢?这不叫嵌套,一个进程包函了n个线程,这些线程共享进程的4GB的虚拟寻址空间你这只不过是在一个线程启动了另一个线程,当然,它们都是属于当前进程的,如果想终止某个线程直接使用建立线程返回的那个名柄将其结束就可以了.
2. systemServer通过每个服务类(WifiService)的全类名(com.android.server.wifi.WifiService),利用反射机制来启动调用对应的服务类。并且通过Binder通信和对应的**.impl(WifiServiceImpl)绑定
WifiService重写的SystemService方法,真正实现是在WifiServiceImpl中的,上面有两行比较重要
mImpl = new WifiServiceImpl(context, new WifiInjector(context), new WifiAsyncChannel(TAG));
创建WifiServiceImpl实现类,并且创建了WifiInjector,WifiInjector中包含了WifiStateMachine,WifiController,WifiNative等重要属性的初始化工作
publishBinderService(Context.WIFI_SERVICE, mImpl);
publishBinderService函数是SystemService中的函数,最后是调用了ServiceManager.addService SystemServer启动系统服务的时候,会将每个服务的服务端实现类通过ServiceManager.addService 来让ServiceManager接管管理
2. adb logcat -c; adb logcat -G 50M | adb logcat | grep -E "WifiTetherSwitchBarController|AndroidRuntime"
2. 我们在各种manager代码中会定义很多的intent 的广播消息, 我们都是通过BROADCAST_INTENT_ACTION 这个来指定的。通过搜素这个KEY字段就可以知道的。 通过mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 这个API 把消息广播出去的。
2. 看上去是手机成功向AP发送DHCP 请求,但是DHCP没有任何回应,导致拿不到IP。DHCP DHCP Discover - Transaction ID 0x613f2b73
2. 状态机的理解:
https://blog.youkuaiyun.com/weixin_38503885/article/details/96490043
StateMachine流程
super(name) 构造SmHandler初始化消息循环
addState 添加当前的状态到mStateInfo集合
setInitialState 设置初始状态
start() 启动当前状态机,在start方法里完成了状态树的构建,并且计算当前所有树的最大深度,并且创建长度为最大深度的数组mStateStack,mStateStack中依次保存从根状态到当前状态的所有状态,并且当前状态在最后一个索引存放, 最后发送 消息,依次执行,从根状态到当前初始状态的enter方法,完成之后,通过performTransitions切换到初始状态
sendMessage 发送SM_INIT_CMD消息,最终是由当前状态的processMsg处理
对于当前状态机接收到的消息,首先由当前的状态处理,如果当前的状态不能处理该消息,则由其父状态去处理,如果所有的状态都不能处理,则会走到其父类StateMachine#unhandledMessage方法
deferMessage是将当前消息先临时存放到mDeferredMessages集合中,等到切换到新的状态,在依次从mDeferredMessages集合中取出消息,在新的状态处理,后defer的消息会优先被处理
transitionTo切换状态并不能立即切换到目的状态,而是现将目的状态保存起来,等到当前状态处理当前消息完成以后,才通过performTransitions切换到目的状态
从当前状态切换到目的状态,首先会计算出当前状态和目的状态的公共父亲状态,然后,从当前状态到公共父亲状态依次执行exit方法,在从父亲状态到目的状态依次执行enter方法(需要注意的是,公共父亲状态不参与exit和enter方法的执行)
https://www.cnblogs.com/JianXu/p/5468839.html adb logcat 需要进一步学习一下
3. WiFi的打开流程 https://blog.youkuaiyun.com/mockingbirds/article/details/78993255
3. protected boolean isConnectedToMobile() {
return (mCm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_MOBILE);
} //
// Return true if device is currently connected to Wifi
protected boolean isConnectedToWifi() {
return (mCm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI);
public static final int TYPE_MOBILE = 0; public static final int TYPE_WIFI = 1;
4. appNotResponding的代码处理流程 (后面要看) https://blog.youkuaiyun.com/u011279649/article/details/53909003
1
public String getTracfoneSimOperatorName() {
String[] arrayATT = {"310410"};
String[] arrayTMobile = {"310260"};
String[] arrayVerizon = {"310004"};
String[] arrayClaro = {"330110"};
String mccmnc = getSimOperator();
String TracfoneSimOperatorName = "";
if (!TextUtils.isEmpty(mccmnc)) {
if (Arrays.asList(arrayATT).contains(mccmnc))
TracfoneSimOperatorName = "ATT";
else if (Arrays.asList(arrayTMobile).contains(mccmnc))
TracfoneSimOperatorName = "TMO";
else if (Arrays.asList(arrayVerizon).contains(mccmnc))
TracfoneSimOperatorName = "VZW";
else if (Arrays.asList(arrayClaro).contains(mccmnc))
TracfoneSimOperatorName = "CLARO";
}
return TracfoneSimOperatorName;
}
2.
public boolean onSwitchToggled(boolean isChecked) {
if(!isChecked){
stopTether();
}else if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_DISABLED)
{
mStatusBar.disable(STATUS_BAR_DISABLED_VALUE_WITHOUT_BACK);
AlertDialog tetherDialog = new AlertDialog.Builder(mContext)
.setTitle(R.string.wifi_hotspot_check_wifi_status)
.setMessage(R.string.wifi_hotspot_switch_notification)
.setCancelable(false)
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mSwitchBar.setChecked(false);
mSwitchBar.setEnabled(true);
mStatusBar.disable(0x00000000);
}
})
.setPositiveButton(R.string.dialog_background_check_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mStatusBar.disable(0x00000000);
startTether();
}
}).create();
tetherDialog.show();
}
return true;
}
3.
public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) {
if (!SystemProperties.getBoolean("persist.tinno.tmp", false)){
OkHttpClient client = new OkHttpClient();
Request request= new Request.Builder().url("http://entitlement.mobile.att.net/mhs1").build();
Call call = client.newCall(request);
call.enqueue(new Callback(){
@Override
public void onFailure(Request request,IOException e) {
String operator = null;
telephonyService = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if(telephonyService != null){
operator = telephonyService.isATTOrCricketSim();
}
mLooper.prepare();
Log.d(TAG, "zgs onFailure");
if(operator.equals("ATT")){
Toast.makeText(mContext,com.android.internal.R.string.wifi_hotspot_entitlement_check_error,Toast.LENGTH_LONG).show();
}else{
Toast.makeText(mContext,com.android.internal.R.string.wifi_hotspot_entitlement_check_cricket_error,Toast.LENGTH_LONG).show();
}
mLooper.loop();
}
@Override
public void onResponse(Response response)
{
Log.d(TAG, "zgs successful code is "+response.code());
String operator = null;
telephonyService = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if(telephonyService != null){
operator = telephonyService.isATTOrCricketSim();
}
int status=response.code();
mLooper.prepare();
switch(status){
case 200:
timer=new Timer(true);
{
try {
WifiManager.result = mService.startSoftAp(wifiConfig);
timer.schedule(new TimerTask() {
@Override
public void run() {
EntitlementCheck();
}
}, 86400000, 86400000);//Delay 24H
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
break;
case 403:
if(operator.equals("ATT")){
Toast.makeText(mContext,com.android.internal.R.string.wifi_hotspot_entitlement_check_403,Toast.LENGTH_LONG).show();
}else{
Toast.makeText(mContext,com.android.internal.R.string.wifi_hotspot_entitlement_check_cricket_403,Toast.LENGTH_LONG).show();
}
break;
default:
if(operator.equals("ATT")){
Toast.makeText(mContext,com.android.internal.R.string.wifi_hotspot_entitlement_check_error,Toast.LENGTH_LONG).show();
}else{
Toast.makeText(mContext,com.android.internal.R.string.wifi_hotspot_entitlement_check_cricket_error,Toast.LENGTH_LONG).show();
}
break;
}
mLooper.loop();
}
});
return WifiManager.result;
}else{
try {
return mService.startSoftAp(wifiConfig);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
4.
//tinno-begin add by zgs for eap-aka
Log.d(TAG, "zgs Michael: updateAccessPointPreferences --- 2");
int isWifiAutoConnect = Settings.Global.getInt(getActivity().getContentResolver(),Settings.Global.WIFI_AUTO_CONNECT_STATUS, 1);
Log.d(TAG,"zgs value is "+isWifiAutoConnect);
String mVodafoneAP = "VodafoneWiFi";
Log.d(TAG,"zgs value is "+accessPoint.getSsidStr());
if(mVodafoneAP.equals(accessPoint.getSsidStr())&&(isWifiAutoConnect==1))
{
//Log.d(TAG, "Michael: updateAccessPoints : " + accessPoints);
Log.d(TAG, "Michael zgs: Auto reconnect to VodafoneWiFi !!! ");
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
WifiConfiguration wifi = new WifiConfiguration();
wifi.SSID = mVodafoneAP;// "\"eap-aka-ansi\"";
wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.AKA);
wifi.enterpriseConfig = enterpriseConfig;
Log.d(TAG, "zgs Finding WiFi network ID:");
wifi.networkId = ssidToNetworkId(wifi.SSID);
Log.d(TAG, "zgs Michael: wifi.networkId: " + wifi.networkId);
if (wifi.networkId == -1) {
Log.d(TAG, "zgs WiFi not found - adding it.");
mWifiManager.addNetwork(wifi);
} else {
Log.d(TAG, "zgs WiFi found - updating it.");
mWifiManager.updateNetwork(wifi);
}
Log.d(TAG, "zgs Saving config.\n");
mWifiManager.saveConfiguration();
wifi.networkId = ssidToNetworkId(wifi.SSID);
Log.d(TAG, "zgs VodafoneWiFi ID = " + wifi.networkId);
SupplicantState supState;
int networkIdToConnect = wifi.networkId;
Log.d(TAG, "zgs Michael: networkIdToConnect 1 = " + networkIdToConnect);
if (networkIdToConnect >= 0) {
//Log.d(TAG, "Start connecting...\n");
Log.d(TAG, "zgs Michael: Reconnect to VodafoneWiFi");
// We disable the network before connecting, because if this was the last connection before
// a disconnect(), this will not reconnect.
mWifiManager.disableNetwork(networkIdToConnect);
mWifiManager.enableNetwork(networkIdToConnect, true);
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
supState = wifiInfo.getSupplicantState();
Log.d(TAG, "zgs Michael: Done connect to network : status = " + supState.toString());
} else {
Log.d(TAG, "zgs manually addNetwork again");
networkIdToConnect = mWifiManager.addNetwork(wifi);
Log.d(TAG, "zgs Michael: networkIdToConnect 2 = " + networkIdToConnect);
mWifiManager.enableNetwork(networkIdToConnect, true);
Log.d(TAG, "zgs Michael: Connect to VodafoneWiFi again");
/*
boolean connected = mWifiManager.reconnect();
Log.d(TAG, "zgs Michael: connected = " + connected);
if(connected)
Log.d(TAG, "zgs Michael: Connect VodafoneWiFi successfully");
*/
}
}
//tinno-end
如何打印字符串数组:
package testArray;
import java.util.Arrays;
public class testArray {
public static void main(String[] args) {
int [] a = new int[5];
int [] b = {1,45,65,345};
String[] c = {"dewd","fwefew","fewf"};
for(int i = 0; i < 5; i ++){
a[i] = i;
}
System.out.println("数组A: " + Arrays.asList(a));
System.out.println("数组B: " +Arrays.toString(b));
System.out.println("字符串C: " +Arrays.toString(c));
System.out.println("字符串C: " +Arrays.asList(c));
}
}
24小时tethering检查:
intent pendingIntent 启动service
intentAlarm = new Intent(getActivity(), TetherBtService.class);
intentAlarm.setAction(ATT_ENTITLEMENT);
pendingIntentAlarm = PendingIntent.getService(getActivity(), 0, intentAlarm, 0);
intentAlarm = new Intent(); //启动BroadcastReceiver
intentAlarm.setAction(ATT_ENTITLEMENT);
pendingIntentAlarm = PendingIntent.getBroadcast(getActivity(), 0, intentAlarm, 0);
IntentFilter filter = new IntentFilter();
filter.addAction(ATT_ENTITLEMENT);
mEntitlementReceiver = new BroadcastReceiver(){ }
OkHttp设置连接超时时间
Thread thread = new Thread(){
@Override
public void run() {
super.run();
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间
.readTimeout(20, TimeUnit.SECONDS)//设置读取超时时间
.build();
Request.Builder builder = new Request.Builder().url("http://10.7.5.144/oos");
Call call = client.newCall(builder.build());
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
// e.printStackTrace();
if(e instanceof SocketTimeoutException){//判断超时异常
}
if(e instanceof ConnectException){//判断连接异常,我这里是报Failed to connect to 10.7.5.144
}
}
}
};
thread.start();
在代码中监测 数据网络的状态:
mTelephonyMgr.listen(new PhoneStateListener(){
@Override
public void onDataConnectionStateChanged(int state) {
switch(state){
case TelephonyManager.DATA_DISCONNECTED:
MobileDataConnected=false;
break;
case TelephonyManager.DATA_CONNECTING:
break;
case TelephonyManager.DATA_CONNECTED:
MobileDataConnected=true;
break;
} }
}, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
//执行上面的 mTelephonyMgr.listen(PhoneStateListener实例, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE); 后,在上面的onDataConnectionStateChanged就可以执行对应的状态。这段代码其实是引用了mTelephonyMgr.listen()函数
trim()函数可以自动去掉首尾的空格: if ((TextUtils.isEmpty(ssid))||(TextUtils.isEmpty(ssid.trim())))
PhoneStatusBarPolicy 这个类是定义所有在StatusBar上的ICON的显示机制。比如蓝牙updateBluetooth,热点,WIFI 等。
通过INTENT 跳转ACTIVITY的, 主要是通过构造INTENT 的ACTION , putExtra 主要是给需要跳转的ACTIVITY传递对应的参数。一般在对应的ACTIVITY的对应的manifest.xml文件中,会定义<action android:name="android.net.conn.CAPTIVE_PORTAL"/> 这种。 比如下面的这段代码:{
final Intent intent = new Intent(
ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network);
intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
new CaptivePortal(new ICaptivePortal.Stub() {
@Override
public void appResponse(int response) {
if (response == APP_RETURN_WANTED_AS_IS) {
mContext.enforceCallingPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL,
"CaptivePortal");
}
sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
}
}));
intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, mLastPortalProbeResult.detectUrl);
intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, mCaptivePortalUserAgent);
intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
如何判断当前是不是主线程?
public boolean isMainThread() {
return Looper.getMainLooper() == Looper.myLooper();
}
public boolean isMainThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
public boolean isMainThread() {
return Looper.getMainLooper().getThread().getId() == Thread.currentThread().getId();
}
runtime 判断当前网络有没有外网能力:
public boolean isNetworkOnline() {
Runtime runtime = Runtime.getRuntime();
try {
Process ipProcess = runtime.exec("ping -c 3 www.baidu.com");
int exitValue = ipProcess.waitFor();
Log.i("Avalible", "Process:"+exitValue);
return (exitValue == 0);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return false;
}
如何判断在一个字符串数组里有没有一个字符串
public boolean isSimCarSupportMHT() {
String[] arrayATTnotSupportMHT = {"310410", "311180", "310280", "310030"};
String mccmnc = null;
mccmnc = getSimOperatorNumericForPhone(0);
if (!TextUtils.isEmpty(mccmnc)) {
if (Arrays.asList(arrayATTnotSupportMHT).contains(mccmnc)){
Log.d(TAG, "++error! this sim's plmn not support MHN");
return false;
}
}else{
Log.d(TAG, "++error! mccmnc is null");
return false;
}
return true;
}