1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.android.iwlan;
18 
19 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
21 
22 import android.content.Context;
23 import android.content.Intent;
24 import android.net.ConnectivityManager;
25 import android.net.LinkProperties;
26 import android.net.Network;
27 import android.net.NetworkCapabilities;
28 import android.net.NetworkSpecifier;
29 import android.net.TelephonyNetworkSpecifier;
30 import android.net.TransportInfo;
31 import android.net.vcn.VcnTransportInfo;
32 import android.os.Handler;
33 import android.os.HandlerExecutor;
34 import android.os.HandlerThread;
35 import android.os.IBinder;
36 import android.os.Looper;
37 import android.os.Message;
38 import android.support.annotation.NonNull;
39 import android.telephony.AccessNetworkConstants;
40 import android.telephony.NetworkRegistrationInfo;
41 import android.telephony.NetworkService;
42 import android.telephony.NetworkServiceCallback;
43 import android.telephony.SubscriptionManager;
44 import android.telephony.TelephonyManager;
45 import android.util.Log;
46 
47 import com.android.internal.annotations.VisibleForTesting;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Objects;
53 import java.util.concurrent.ConcurrentHashMap;
54 
55 public class IwlanNetworkService extends NetworkService {
56     private static final String TAG = IwlanNetworkService.class.getSimpleName();
57     private static Context mContext;
58     private IwlanNetworkMonitorCallback mNetworkMonitorCallback;
59     private IwlanOnSubscriptionsChangedListener mSubsChangeListener;
60     private Handler mIwlanNetworkServiceHandler;
61     private HandlerThread mIwlanNetworkServiceHandlerThread;
62     private static boolean sNetworkConnected;
63     private static final Map<Integer, IwlanNetworkServiceProvider> sIwlanNetworkServiceProviders =
64             new ConcurrentHashMap<>();
65     private static final int INVALID_SUB_ID = -1;
66 
67     // The current subscription with the active internet PDN. Need not be the default data sub.
68     // If internet is over WiFi, this value will be INVALID_SUB_ID.
69     private static int mConnectedDataSub = INVALID_SUB_ID;
70 
71     private static final int EVENT_BASE = IwlanEventListener.NETWORK_SERVICE_INTERNAL_EVENT_BASE;
72     private static final int EVENT_NETWORK_REGISTRATION_INFO_REQUEST = EVENT_BASE;
73     private static final int EVENT_CREATE_NETWORK_SERVICE_PROVIDER = EVENT_BASE + 1;
74     private static final int EVENT_REMOVE_NETWORK_SERVICE_PROVIDER = EVENT_BASE + 2;
75 
76     @VisibleForTesting
77     enum Transport {
78         UNSPECIFIED_NETWORK,
79         MOBILE,
80         WIFI
81     }
82 
83     private static Transport sDefaultDataTransport = Transport.UNSPECIFIED_NETWORK;
84 
85     // This callback runs in the same thread as IwlanNetworkServiceHandler
86     final class IwlanNetworkMonitorCallback extends ConnectivityManager.NetworkCallback {
87         /** Called when the framework connects and has declared a new network ready for use. */
88         @Override
onAvailable(Network network)89         public void onAvailable(Network network) {
90             Log.d(TAG, "onAvailable: " + network);
91         }
92 
93         /**
94          * Called when the network is about to be lost, typically because there are no outstanding
95          * requests left for it. This may be paired with a {@link
96          * ConnectivityManager.NetworkCallback#onAvailable} call with the new replacement network
97          * for graceful handover. This method is not guaranteed to be called before {@link
98          * ConnectivityManager.NetworkCallback#onLost} is called, for example in case a network is
99          * suddenly disconnected.
100          */
101         @Override
onLosing(Network network, int maxMsToLive)102         public void onLosing(Network network, int maxMsToLive) {
103             Log.d(TAG, "onLosing: maxMsToLive: " + maxMsToLive + " network: " + network);
104         }
105 
106         /**
107          * Called when a network disconnects or otherwise no longer satisfies this request or
108          * callback.
109          */
110         @Override
onLost(Network network)111         public void onLost(Network network) {
112             Log.d(TAG, "onLost: " + network);
113             IwlanNetworkService.setConnectedDataSub(INVALID_SUB_ID);
114             IwlanNetworkService.setNetworkConnected(false, Transport.UNSPECIFIED_NETWORK);
115         }
116 
117         /** Called when the network corresponding to this request changes {@link LinkProperties}. */
118         @Override
onLinkPropertiesChanged(Network network, LinkProperties linkProperties)119         public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
120             Log.d(TAG, "onLinkPropertiesChanged: " + linkProperties);
121         }
122 
123         /** Called when access to the specified network is blocked or unblocked. */
124         @Override
onBlockedStatusChanged(Network network, boolean blocked)125         public void onBlockedStatusChanged(Network network, boolean blocked) {
126             // TODO: check if we need to handle this
127             Log.d(TAG, "onBlockedStatusChanged: " + " BLOCKED:" + blocked);
128         }
129 
130         @Override
onCapabilitiesChanged( Network network, NetworkCapabilities networkCapabilities)131         public void onCapabilitiesChanged(
132                 Network network, NetworkCapabilities networkCapabilities) {
133             // onCapabilitiesChanged is guaranteed to be called immediately after onAvailable per
134             // API
135             Log.d(TAG, "onCapabilitiesChanged: " + network);
136             if (networkCapabilities != null) {
137                 if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
138                     IwlanNetworkService.setConnectedDataSub(
139                             getConnectedDataSub(networkCapabilities));
140                     IwlanNetworkService.setNetworkConnected(
141                             true, IwlanNetworkService.Transport.MOBILE);
142                 } else if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
143                     IwlanNetworkService.setConnectedDataSub(INVALID_SUB_ID);
144                     IwlanNetworkService.setNetworkConnected(
145                             true, IwlanNetworkService.Transport.WIFI);
146                 } else {
147                     Log.w(TAG, "Network does not have cellular or wifi capability");
148                 }
149             }
150         }
151     }
152 
153     final class IwlanOnSubscriptionsChangedListener
154             extends SubscriptionManager.OnSubscriptionsChangedListener {
155         /**
156          * Callback invoked when there is any change to any SubscriptionInfo. Typically, this method
157          * invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
158          */
159         @Override
onSubscriptionsChanged()160         public void onSubscriptionsChanged() {
161             for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviders.values()) {
162                 np.subscriptionChanged();
163             }
164         }
165     }
166 
167     @VisibleForTesting
168     class IwlanNetworkServiceProvider extends NetworkServiceProvider {
169         private final IwlanNetworkService mIwlanNetworkService;
170         private final String SUB_TAG;
171         private boolean mIsSubActive = false;
172 
173         /**
174          * Constructor
175          *
176          * @param slotIndex SIM slot id the data service provider associated with.
177          */
IwlanNetworkServiceProvider(int slotIndex, IwlanNetworkService iwlanNetworkService)178         public IwlanNetworkServiceProvider(int slotIndex, IwlanNetworkService iwlanNetworkService) {
179             super(slotIndex);
180             SUB_TAG = TAG + "[" + slotIndex + "]";
181             mIwlanNetworkService = iwlanNetworkService;
182 
183             // Register IwlanEventListener
184             List<Integer> events = new ArrayList<Integer>();
185             events.add(IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT);
186             events.add(IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT);
187             IwlanEventListener.getInstance(mContext, slotIndex)
188                     .addEventListener(events, getIwlanNetworkServiceHandler());
189         }
190 
191         @Override
requestNetworkRegistrationInfo(int domain, NetworkServiceCallback callback)192         public void requestNetworkRegistrationInfo(int domain, NetworkServiceCallback callback) {
193             getIwlanNetworkServiceHandler()
194                     .sendMessage(
195                             getIwlanNetworkServiceHandler()
196                                     .obtainMessage(
197                                             EVENT_NETWORK_REGISTRATION_INFO_REQUEST,
198                                             new NetworkRegistrationInfoRequestData(
199                                                     domain, callback, this)));
200         }
201 
202         /**
203          * Called when the instance of network service is destroyed (e.g. got unbind or binder died)
204          * or when the network service provider is removed. The extended class should implement this
205          * method to perform cleanup works.
206          */
207         @Override
close()208         public void close() {
209             mIwlanNetworkService.removeNetworkServiceProvider(this);
210             IwlanEventListener.getInstance(mContext, getSlotIndex())
211                     .removeEventListener(getIwlanNetworkServiceHandler());
212         }
213 
214         @VisibleForTesting
subscriptionChanged()215         void subscriptionChanged() {
216             boolean subActive =
217                     getSubscriptionManager()
218                                     .getActiveSubscriptionInfoForSimSlotIndex(getSlotIndex())
219                             != null;
220             if (subActive == mIsSubActive) {
221                 return;
222             }
223             mIsSubActive = subActive;
224             if (subActive) {
225                 Log.d(SUB_TAG, "sub changed from not_ready --> ready");
226             } else {
227                 Log.d(SUB_TAG, "sub changed from ready --> not_ready");
228             }
229 
230             notifyNetworkRegistrationInfoChanged();
231         }
232     }
233 
234     private final class IwlanNetworkServiceHandler extends Handler {
235         private final String TAG = IwlanNetworkServiceHandler.class.getSimpleName();
236 
237         @Override
handleMessage(Message msg)238         public void handleMessage(Message msg) {
239             Log.d(TAG, "msg.what = " + eventToString(msg.what));
240 
241             IwlanNetworkServiceProvider iwlanNetworkServiceProvider;
242             int slotId;
243 
244             switch (msg.what) {
245                 case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT:
246                 case IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT:
247                     iwlanNetworkServiceProvider = getNetworkServiceProvider(msg.arg1);
248                     iwlanNetworkServiceProvider.notifyNetworkRegistrationInfoChanged();
249                     break;
250 
251                 case EVENT_NETWORK_REGISTRATION_INFO_REQUEST:
252                     NetworkRegistrationInfoRequestData networkRegistrationInfoRequestData =
253                             (NetworkRegistrationInfoRequestData) msg.obj;
254                     int domain = networkRegistrationInfoRequestData.mDomain;
255                     NetworkServiceCallback callback = networkRegistrationInfoRequestData.mCallback;
256                     iwlanNetworkServiceProvider =
257                             networkRegistrationInfoRequestData.mIwlanNetworkServiceProvider;
258 
259                     if (callback == null) {
260                         Log.d(TAG, "Error: callback is null. returning");
261                         return;
262                     }
263                     if (domain != NetworkRegistrationInfo.DOMAIN_PS) {
264                         callback.onRequestNetworkRegistrationInfoComplete(
265                                 NetworkServiceCallback.RESULT_ERROR_UNSUPPORTED, null);
266                         return;
267                     }
268 
269                     NetworkRegistrationInfo.Builder nriBuilder =
270                             new NetworkRegistrationInfo.Builder();
271                     nriBuilder
272                             .setAvailableServices(
273                                     List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
274                             .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
275                             .setEmergencyOnly(!iwlanNetworkServiceProvider.mIsSubActive)
276                             .setDomain(NetworkRegistrationInfo.DOMAIN_PS);
277 
278                     slotId = iwlanNetworkServiceProvider.getSlotIndex();
279                     if (!IwlanNetworkService.isNetworkConnected(
280                             isActiveDataOnOtherSub(slotId),
281                             IwlanHelper.isCrossSimCallingEnabled(mContext, slotId))) {
282                         nriBuilder
283                                 .setRegistrationState(
284                                         NetworkRegistrationInfo
285                                                 .REGISTRATION_STATE_NOT_REGISTERED_SEARCHING)
286                                 .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UNKNOWN);
287                         Log.d(
288                                 TAG + "[" + slotId + "]",
289                                 ": reg state" + " REGISTRATION_STATE_NOT_REGISTERED_SEARCHING");
290                     } else {
291                         nriBuilder
292                                 .setRegistrationState(
293                                         NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
294                                 .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN);
295                         Log.d(TAG + "[" + slotId + "]", ": reg state REGISTRATION_STATE_HOME");
296                     }
297 
298                     callback.onRequestNetworkRegistrationInfoComplete(
299                             NetworkServiceCallback.RESULT_SUCCESS, nriBuilder.build());
300                     break;
301 
302                 case EVENT_CREATE_NETWORK_SERVICE_PROVIDER:
303                     iwlanNetworkServiceProvider = (IwlanNetworkServiceProvider) msg.obj;
304 
305                     if (sIwlanNetworkServiceProviders.isEmpty()) {
306                         initCallback();
307                     }
308 
309                     addIwlanNetworkServiceProvider(iwlanNetworkServiceProvider);
310                     break;
311 
312                 case EVENT_REMOVE_NETWORK_SERVICE_PROVIDER:
313                     iwlanNetworkServiceProvider = (IwlanNetworkServiceProvider) msg.obj;
314                     slotId = iwlanNetworkServiceProvider.getSlotIndex();
315                     IwlanNetworkServiceProvider nsp = sIwlanNetworkServiceProviders.remove(slotId);
316                     if (nsp == null) {
317                         Log.w(
318                                 TAG + "[" + slotId + "]",
319                                 "No NetworkServiceProvider exists for slot!");
320                         return;
321                     }
322                     if (sIwlanNetworkServiceProviders.isEmpty()) {
323                         deinitCallback();
324                     }
325                     break;
326 
327                 default:
328                     throw new IllegalStateException("Unexpected value: " + msg.what);
329             }
330         }
331 
IwlanNetworkServiceHandler(Looper looper)332         IwlanNetworkServiceHandler(Looper looper) {
333             super(looper);
334         }
335     }
336 
337     private static final class NetworkRegistrationInfoRequestData {
338         final int mDomain;
339         final NetworkServiceCallback mCallback;
340         final IwlanNetworkServiceProvider mIwlanNetworkServiceProvider;
341 
NetworkRegistrationInfoRequestData( int domain, NetworkServiceCallback callback, IwlanNetworkServiceProvider nsp)342         private NetworkRegistrationInfoRequestData(
343                 int domain, NetworkServiceCallback callback, IwlanNetworkServiceProvider nsp) {
344             mDomain = domain;
345             mCallback = callback;
346             mIwlanNetworkServiceProvider = nsp;
347         }
348     }
349 
350     /**
351      * Create the instance of {@link NetworkServiceProvider}. Network service provider must override
352      * this method to facilitate the creation of {@link NetworkServiceProvider} instances. The
353      * system will call this method after binding the network service for each active SIM slot id.
354      *
355      * @param slotIndex SIM slot id the network service associated with.
356      * @return Network service object. Null if failed to create the provider (e.g. invalid slot
357      *     index)
358      */
359     @Override
onCreateNetworkServiceProvider(int slotIndex)360     public NetworkServiceProvider onCreateNetworkServiceProvider(int slotIndex) {
361         Log.d(TAG, "onCreateNetworkServiceProvider: slotidx:" + slotIndex);
362 
363         // TODO: validity check slot index
364 
365         IwlanNetworkServiceProvider np = new IwlanNetworkServiceProvider(slotIndex, this);
366         getIwlanNetworkServiceHandler()
367                 .sendMessage(
368                         getIwlanNetworkServiceHandler()
369                                 .obtainMessage(EVENT_CREATE_NETWORK_SERVICE_PROVIDER, np));
370         return np;
371     }
372 
setConnectedDataSub(int subId)373     static void setConnectedDataSub(int subId) {
374         mConnectedDataSub = subId;
375     }
376 
getConnectedDataSub(NetworkCapabilities networkCapabilities)377     static int getConnectedDataSub(NetworkCapabilities networkCapabilities) {
378         int connectedDataSub = INVALID_SUB_ID;
379         NetworkSpecifier specifier = networkCapabilities.getNetworkSpecifier();
380         TransportInfo transportInfo = networkCapabilities.getTransportInfo();
381 
382         if (specifier instanceof TelephonyNetworkSpecifier) {
383             connectedDataSub = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
384         } else if (transportInfo instanceof VcnTransportInfo) {
385             connectedDataSub = ((VcnTransportInfo) transportInfo).getSubId();
386         }
387         return connectedDataSub;
388     }
389 
isActiveDataOnOtherSub(int slotId)390     static boolean isActiveDataOnOtherSub(int slotId) {
391         int subId = IwlanHelper.getSubId(mContext, slotId);
392         return mConnectedDataSub != INVALID_SUB_ID && subId != mConnectedDataSub;
393     }
394 
isNetworkConnected(boolean isActiveDataOnOtherSub, boolean isCstEnabled)395     public static boolean isNetworkConnected(boolean isActiveDataOnOtherSub, boolean isCstEnabled) {
396         if (isActiveDataOnOtherSub && isCstEnabled) {
397             // For cross-SIM IWLAN (Transport.MOBILE), an active data PDN must be maintained on the
398             // other subscription.
399             if (sNetworkConnected && (sDefaultDataTransport != Transport.MOBILE)) {
400                 Log.e(TAG, "Internet is on other slot, but default transport is not MOBILE!");
401             }
402             return sNetworkConnected;
403         } else {
404             // For all other cases, only wifi transport can be used.
405             return ((sDefaultDataTransport == Transport.WIFI) && sNetworkConnected);
406         }
407     }
408 
setNetworkConnected(boolean connected, Transport transport)409     public static void setNetworkConnected(boolean connected, Transport transport) {
410         if (connected == sNetworkConnected && transport == sDefaultDataTransport) {
411             return;
412         }
413         if (connected && (transport == IwlanNetworkService.Transport.UNSPECIFIED_NETWORK)) {
414             return;
415         }
416         sNetworkConnected = connected;
417         sDefaultDataTransport = transport;
418 
419         for (IwlanNetworkServiceProvider np : sIwlanNetworkServiceProviders.values()) {
420             np.notifyNetworkRegistrationInfoChanged();
421         }
422     }
423 
addIwlanNetworkServiceProvider(IwlanNetworkServiceProvider np)424     void addIwlanNetworkServiceProvider(IwlanNetworkServiceProvider np) {
425         int slotIndex = np.getSlotIndex();
426         if (sIwlanNetworkServiceProviders.containsKey(slotIndex)) {
427             throw new IllegalStateException(
428                     "NetworkServiceProvider already exists for slot " + slotIndex);
429         }
430         sIwlanNetworkServiceProviders.put(slotIndex, np);
431     }
432 
removeNetworkServiceProvider(IwlanNetworkServiceProvider np)433     public void removeNetworkServiceProvider(IwlanNetworkServiceProvider np) {
434         getIwlanNetworkServiceHandler()
435                 .sendMessage(
436                         getIwlanNetworkServiceHandler()
437                                 .obtainMessage(EVENT_REMOVE_NETWORK_SERVICE_PROVIDER, np));
438     }
439 
initCallback()440     void initCallback() {
441         // register for default network callback
442         mNetworkMonitorCallback = new IwlanNetworkMonitorCallback();
443         getConnectivityManager()
444                 .registerSystemDefaultNetworkCallback(
445                         mNetworkMonitorCallback, getIwlanNetworkServiceHandler());
446         Log.d(TAG, "Registered with Connectivity Service");
447 
448         /* register with subscription manager */
449         mSubsChangeListener = new IwlanOnSubscriptionsChangedListener();
450         getSubscriptionManager()
451                 .addOnSubscriptionsChangedListener(
452                         new HandlerExecutor(getIwlanNetworkServiceHandler()), mSubsChangeListener);
453         Log.d(TAG, "Registered with Subscription Service");
454     }
455 
deinitCallback()456     void deinitCallback() {
457         // deinit network related stuff
458         getConnectivityManager().unregisterNetworkCallback(mNetworkMonitorCallback);
459         mNetworkMonitorCallback = null;
460 
461         // deinit subscription manager related stuff
462         getSubscriptionManager().removeOnSubscriptionsChangedListener(mSubsChangeListener);
463         mSubsChangeListener = null;
464         if (mIwlanNetworkServiceHandlerThread != null) {
465             mIwlanNetworkServiceHandlerThread.quit();
466             mIwlanNetworkServiceHandlerThread = null;
467         }
468         mIwlanNetworkServiceHandler = null;
469     }
470 
471     @VisibleForTesting
setAppContext(Context appContext)472     void setAppContext(Context appContext) {
473         mContext = appContext;
474     }
475 
476     @VisibleForTesting
getNetworkServiceProvider(int slotIndex)477     IwlanNetworkServiceProvider getNetworkServiceProvider(int slotIndex) {
478         return sIwlanNetworkServiceProviders.get(slotIndex);
479     }
480 
481     @VisibleForTesting
getNetworkMonitorCallback()482     IwlanNetworkMonitorCallback getNetworkMonitorCallback() {
483         return mNetworkMonitorCallback;
484     }
485 
486     @VisibleForTesting
487     @NonNull
getIwlanNetworkServiceHandler()488     Handler getIwlanNetworkServiceHandler() {
489         if (mIwlanNetworkServiceHandler == null) {
490             mIwlanNetworkServiceHandler = new IwlanNetworkServiceHandler(getLooper());
491         }
492         return mIwlanNetworkServiceHandler;
493     }
494 
495     @VisibleForTesting
getLooper()496     Looper getLooper() {
497         mIwlanNetworkServiceHandlerThread = new HandlerThread("IwlanNetworkServiceThread");
498         mIwlanNetworkServiceHandlerThread.start();
499         return mIwlanNetworkServiceHandlerThread.getLooper();
500     }
501 
eventToString(int event)502     private static String eventToString(int event) {
503         switch (event) {
504             case IwlanEventListener.CROSS_SIM_CALLING_ENABLE_EVENT:
505                 return "CROSS_SIM_CALLING_ENABLE_EVENT";
506             case IwlanEventListener.CROSS_SIM_CALLING_DISABLE_EVENT:
507                 return "CROSS_SIM_CALLING_DISABLE_EVENT";
508             case EVENT_NETWORK_REGISTRATION_INFO_REQUEST:
509                 return "EVENT_NETWORK_REGISTRATION_INFO_REQUEST";
510             case EVENT_CREATE_NETWORK_SERVICE_PROVIDER:
511                 return "EVENT_CREATE_NETWORK_SERVICE_PROVIDER";
512             case EVENT_REMOVE_NETWORK_SERVICE_PROVIDER:
513                 return "EVENT_REMOVE_NETWORK_SERVICE_PROVIDER";
514             default:
515                 return "Unknown(" + event + ")";
516         }
517     }
518 
519     @Override
onCreate()520     public void onCreate() {
521         mContext = getApplicationContext();
522     }
523 
524     @Override
onBind(Intent intent)525     public IBinder onBind(Intent intent) {
526         Log.d(TAG, "IwlanNetworkService onBind");
527         return super.onBind(intent);
528     }
529 
530     @NonNull
getConnectivityManager()531     ConnectivityManager getConnectivityManager() {
532         return Objects.requireNonNull(mContext.getSystemService(ConnectivityManager.class));
533     }
534 
535     @NonNull
getSubscriptionManager()536     SubscriptionManager getSubscriptionManager() {
537         return Objects.requireNonNull(mContext.getSystemService(SubscriptionManager.class));
538     }
539 }
540