summaryrefslogtreecommitdiff
path: root/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java
blob: acf84462f1e8d62a03cd940bbc06aaffa7ffd576 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
/*
 * Copyright (c) 2018 Livio, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * Neither the name of the Livio Inc. nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.smartdevicelink.transport;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.TransactionTooLargeException;

import com.smartdevicelink.protocol.SdlPacket;
import com.smartdevicelink.transport.enums.TransportType;
import com.smartdevicelink.transport.utl.ByteAraryMessageAssembler;
import com.smartdevicelink.transport.utl.ByteArrayMessageSpliter;
import com.smartdevicelink.transport.utl.TransportRecord;
import com.smartdevicelink.util.DebugTool;

import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;


public class TransportBroker {

    private static final String TAG = "SdlTransportBroker";

    /**
     * See version document included with library
     */
    private static final int MAX_MESSAGING_VERSION = 2;
    private static final int MIN_MESSAGING_VERSION = 1;

    /** Version of the router service that supports the new additional transports (USB and TCP) */
    private static final int RS_MULTI_TRANSPORT_SUPPORT = 8;
    private static final TransportRecord LEGACY_TRANSPORT_RECORD = new TransportRecord(TransportType.BLUETOOTH,null);

    private final String WHERE_TO_REPLY_PREFIX = "com.sdl.android.";
    private String appId = null;
    private String whereToReply = null;
    private Context currentContext = null;

    private final Object INIT_LOCK = new Object();
    private final Object MESSAGE_SEND_LOCK = new Object();

    Messenger routerServiceMessenger = null;
    final Messenger clientMessenger;

    boolean isBound = false, registeredWithRouterService = false;
    private String routerPackage = null, routerClassName = null;
    private ComponentName routerService = null;


    private SdlPacket bufferedPacket = null;
    private ByteAraryMessageAssembler bufferedPayloadAssembler = null;

    private ServiceConnection routerConnection;
    private int routerServiceVersion = 1;
    private int messagingVersion = MAX_MESSAGING_VERSION;

    private void initRouterConnection() {
        routerConnection = new ServiceConnection() {

            public void onServiceConnected(ComponentName className, IBinder service) {
                DebugTool.logInfo(TAG, "Bound to service " + className.toString());
                routerServiceMessenger = new Messenger(service);
                isBound = true;
                //So we just established our connection
                //Register with router service
                sendRegistrationMessage();
            }

            public void onServiceDisconnected(ComponentName className) {
                DebugTool.logInfo(TAG, "Unbound from service " + className.getClassName());
                routerServiceMessenger = null;
                registeredWithRouterService = false;
                isBound = false;
                onHardwareDisconnected(null, null);
            }
        };
    }

    protected boolean sendMessageToRouterService(Message message) {
        return sendMessageToRouterService(message, 0);
    }

    protected boolean sendMessageToRouterService(Message message, int retryCount) {
        synchronized (MESSAGE_SEND_LOCK) {
            if (message == null) {
                DebugTool.logWarning(TAG, "Attempted to send null message");
                return false;
            }
            //Log.i(TAG, "Attempting to send message type - " + message.what);
            if (isBound && routerServiceMessenger != null && routerServiceMessenger.getBinder() != null && routerServiceMessenger.getBinder().isBinderAlive()) {
                if (registeredWithRouterService
                        || message.what == TransportConstants.ROUTER_REGISTER_CLIENT) { //We can send a message if we are registered or are attempting to register
                    try {
                        routerServiceMessenger.send(message);
                        return true;
                    } catch (RemoteException e) {
                        e.printStackTrace();
                        //Let's check to see if we should retry
                        if (e instanceof TransactionTooLargeException || retryCount < 5 && routerServiceMessenger.getBinder().isBinderAlive() && routerServiceMessenger.getBinder().pingBinder()) { //We probably just failed on a small transaction =\
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e1) {
                                e1.printStackTrace();
                            }
                            return sendMessageToRouterService(message, retryCount++);
                        } else {
                            //DeadObject, time to kill our connection
                            DebugTool.logInfo(TAG, "Dead object while attempting to send packet");
                            stop();
                            onHardwareDisconnected(null, null);
                            return false;
                        }
                    } catch (NullPointerException e) {
                        DebugTool.logInfo(TAG, "Null messenger while attempting to send packet"); // NPE, routerServiceMessenger is null
                        stop();
                        onHardwareDisconnected(null, null);
                        return false;
                    }
                } else {
                    DebugTool.logError(TAG, "Unable to send message to router service. Not registered.");
                    return false;
                }
            } else {
                DebugTool.logError(TAG, "Unable to send message to router service. Not bound.");
                return false;
            }
        }
    }


    /**
     * Handler of incoming messages from service.
     */
    static class ClientHandler extends Handler {
        ClassLoader loader;
        final WeakReference<TransportBroker> provider;

        public ClientHandler(TransportBroker provider) {
            this.provider = new WeakReference<TransportBroker>(provider);
            loader = getClass().getClassLoader();
        }

        @Override
        public void handleMessage(Message msg) {
            TransportBroker broker = provider.get();
            if (broker == null) {
                DebugTool.logError(TAG, "Broker object null, unable to process message");
                return;
            }
            Bundle bundle = msg.getData();

            if (bundle != null) {
                bundle.setClassLoader(loader);
            }
            //Log.d(TAG, "Bundle: "  + bundle.toString());
            /* DO NOT MOVE
             * This needs to be first to make sure we already know if we are attempting to enter legacy mode or not
             */
            if (bundle != null
                    && bundle.containsKey(TransportConstants.ENABLE_LEGACY_MODE_EXTRA)) {
                boolean enableLegacy = bundle.getBoolean(TransportConstants.ENABLE_LEGACY_MODE_EXTRA, false);
                broker.enableLegacyMode(enableLegacy);
            }

            //Find out what message we have and what to do with it
            switch (msg.what) {
                case TransportConstants.ROUTER_REGISTER_CLIENT_RESPONSE:
                    switch (msg.arg1) {
                        case TransportConstants.REGISTRATION_RESPONSE_SUCESS:
                            // yay! we have been registered. Now what?
                            broker.registeredWithRouterService = true;
                            if (bundle != null) {

                                if (bundle.containsKey(TransportConstants.ROUTER_SERVICE_VERSION)) {
                                    broker.routerServiceVersion = bundle.getInt(TransportConstants.ROUTER_SERVICE_VERSION);
                                }

                                if (bundle.containsKey(TransportConstants.HARDWARE_CONNECTED) || bundle.containsKey(TransportConstants.CURRENT_HARDWARE_CONNECTED)) {
                                    //A connection is already available
                                    handleConnectionEvent(bundle, broker);
                                }

                            }
                            break;
                        case TransportConstants.REGISTRATION_RESPONSE_DENIED_LEGACY_MODE_ENABLED:
                            DebugTool.logInfo(TAG, "Denied registration because router is in legacy mode");
                            broker.registeredWithRouterService = false;
                            broker.enableLegacyMode(true);
                            //We call this so we can start the process of legacy connection
                            //onHardwareDisconnected(TransportType.BLUETOOTH);
                            broker.onLegacyModeEnabled();
                            break;
                        default:
                            broker.registeredWithRouterService = false;
                            DebugTool.logWarning(TAG, "Registration denied from router service. Reason - " + msg.arg1);
                            break;
                    }
                    ;


                    break;
                case TransportConstants.ROUTER_UNREGISTER_CLIENT_RESPONSE:
                    if (msg.arg1 == TransportConstants.UNREGISTRATION_RESPONSE_SUCESS) {
                        // We've been unregistered. Now what?


                    } else { //We were denied our unregister request to the router service, let's see why
                        DebugTool.logWarning(TAG, "Unregister request denied from router service. Reason - " + msg.arg1);
                        //Do we care?
                    }

                    break;
                case TransportConstants.ROUTER_RECEIVED_PACKET:
                    if(bundle == null){
                        DebugTool.logWarning(TAG, "Received packet message from router service with no bundle");
                        return;
                    }
                    //So the intent has a packet with it. PEFRECT! Let's send it through the library
                    int flags = bundle.getInt(TransportConstants.BYTES_TO_SEND_FLAGS, TransportConstants.BYTES_TO_SEND_FLAG_NONE);

                    if (bundle.containsKey(TransportConstants.FORMED_PACKET_EXTRA_NAME)) {
                        SdlPacket packet = bundle.getParcelable(TransportConstants.FORMED_PACKET_EXTRA_NAME);

                        if (flags == TransportConstants.BYTES_TO_SEND_FLAG_NONE) {
                            if (packet != null) { //Log.i(TAG, "received packet to process "+  packet.toString());

                                if(packet.getTransportRecord() == null){
                                    // If the transport record is null, one must be added
                                    // This is likely due to an older router service being used
                                    // in which only a bluetooth transport is available
                                    packet.setTransportRecord(LEGACY_TRANSPORT_RECORD);
                                }

                                broker.onPacketReceived(packet);
                            } else {
                                DebugTool.logWarning(TAG, "Received null packet from router service, not passing along");
                            }
                        } else if (flags == TransportConstants.BYTES_TO_SEND_FLAG_SDL_PACKET_INCLUDED) {
                            broker.bufferedPacket = (SdlPacket) packet;
                            if (broker.bufferedPayloadAssembler != null) {
                                broker.bufferedPayloadAssembler.close();
                                broker.bufferedPayloadAssembler = null;
                            }

                            broker.bufferedPayloadAssembler = new ByteAraryMessageAssembler();
                            broker.bufferedPayloadAssembler.init();
                        }
                    } else if (bundle.containsKey(TransportConstants.BYTES_TO_SEND_EXTRA_NAME)) {
                        //This should contain the payload
                        if (broker.bufferedPayloadAssembler != null) {
                            byte[] chunk = bundle.getByteArray(TransportConstants.BYTES_TO_SEND_EXTRA_NAME);
                            if (!broker.bufferedPayloadAssembler.handleMessage(flags, chunk)) {
                                //If there was a problem
                                DebugTool.logError(TAG, "Error handling bytes for split packet");
                            }
                            if (broker.bufferedPayloadAssembler.isFinished()) {
                                broker.bufferedPacket.setPayload(broker.bufferedPayloadAssembler.getBytes());

                                broker.bufferedPayloadAssembler.close();
                                broker.bufferedPayloadAssembler = null;
                                broker.onPacketReceived(broker.bufferedPacket);
                                broker.bufferedPacket = null;
                            }
                        }
                        //}
                        //}
                    } else {
                        DebugTool.logWarning(TAG, "Flase positive packet reception");
                    }
                    break;
                case TransportConstants.HARDWARE_CONNECTION_EVENT:
                    if(bundle == null){
                        DebugTool.logWarning(TAG, "Received hardware connection message from router service with no bundle");
                        return;
                    }
                    if (bundle.containsKey(TransportConstants.TRANSPORT_DISCONNECTED)
                            || bundle.containsKey(TransportConstants.HARDWARE_DISCONNECTED)) {
                        //We should shut down, so call
                        DebugTool.logInfo(TAG, "Hardware disconnected");
                        if (isLegacyModeEnabled()) {
                            broker.onLegacyModeEnabled();
                        } else {
                            if (bundle.containsKey(TransportConstants.TRANSPORT_DISCONNECTED)) {
                                TransportRecord disconnectedTransport = bundle.getParcelable(TransportConstants.TRANSPORT_DISCONNECTED);
                                List<TransportRecord> connectedTransports = bundle.getParcelableArrayList(TransportConstants.CURRENT_HARDWARE_CONNECTED);
                                broker.onHardwareDisconnected(disconnectedTransport, connectedTransports);
                            } else { //bundle contains key TransportConstants.HARDWARE_DISCONNECTED
                                // Since this is an older router service it can be assumed that the
                                // transport is bluetooth
                                broker.onHardwareDisconnected(LEGACY_TRANSPORT_RECORD, null);
                            }


                        }
                        break;
                    }

                    if (bundle.containsKey(TransportConstants.HARDWARE_CONNECTED) || bundle.containsKey(TransportConstants.CURRENT_HARDWARE_CONNECTED)) {
                        //This is a connection event
                        handleConnectionEvent(bundle,broker);
                        break;
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }

        }

        /**
         * Handle a potential connection event. This will adapt legacy router service implementaions
         * into the new multiple transport scheme.
         * @param bundle the received bundle from the router service
         * @param broker reference to the transport broker that this handler exists
         * @return if a connection event was triggered in the supplied broker
         */
        private boolean handleConnectionEvent(Bundle bundle, TransportBroker broker){
            if (broker.routerServiceVersion < RS_MULTI_TRANSPORT_SUPPORT) {
                //Previous versions of the router service only supports a single
                //transport, so this will be the only extra received
                if (bundle.containsKey(TransportConstants.HARDWARE_CONNECTED)) {
                    // Only bluetooth was a supported transport on previous versions of the router
                    // service so the constant legacy bluetooth transport record will be used.
                    broker.onHardwareConnected(Collections.singletonList(LEGACY_TRANSPORT_RECORD));
                    return true;
                }
            } else{
                //Router service supports multiple transport

                if (bundle.containsKey(TransportConstants.CURRENT_HARDWARE_CONNECTED)) {
                    ArrayList<TransportRecord> transports = bundle.getParcelableArrayList(TransportConstants.CURRENT_HARDWARE_CONNECTED);
                    broker.onHardwareConnected(transports);
                    return true;
                }
            }
            return false;
        }

    }


    /***************************************************************************************************************************************
     ***********************************************  Life Cycle  **************************************************************
     ****************************************************************************************************************************************/


    @SuppressLint("SimpleDateFormat")
    public TransportBroker(Context context, String appId, ComponentName service) {
        synchronized (INIT_LOCK) {
            clientMessenger = new Messenger(new ClientHandler(this));
            initRouterConnection();
            //So the user should have set the AppId, lets define where the intents need to be sent
            SimpleDateFormat s = new SimpleDateFormat("hhmmssss"); //So we have a time stamp of the event
            String timeStamp = s.format(new Date(System.currentTimeMillis()));
            if (whereToReply == null) {
                if (appId == null) { //This should really just throw an error
                    whereToReply = WHERE_TO_REPLY_PREFIX + "." + timeStamp;
                } else {
                    whereToReply = WHERE_TO_REPLY_PREFIX + appId + "." + timeStamp;
                }
            }
            //this.appId = appId.concat(timeStamp);
            this.appId = appId;
            currentContext = context;
            //Log.d(TAG, "Registering our reply receiver: " + whereToReply);
            this.routerService = service;
        }
    }

    /**
     * This beings the initial connection with the router service.
     */
    public boolean start() {
        //Log.d(TAG, "Starting up transport broker for " + whereToReply);
        synchronized (INIT_LOCK) {
            if (currentContext == null) {
                throw new IllegalStateException("This instance can't be started since it's local reference of context is null. Ensure when suppling a context to the TransportBroker that it is valid");
            }
            if (routerConnection == null) {
                initRouterConnection();
            }
            //Log.d(TAG, "Registering our reply receiver: " + whereToReply);
            if (!isBound) {
                return registerWithRouterService();
            } else {
                return false;
            }
        }
    }

    public void resetSession() {
        synchronized (INIT_LOCK) {
            unregisterWithRouterService();
            routerServiceMessenger = null;
            unBindFromRouterService();
            isBound = false;
        }
    }

    /**
     * This method will end our communication with the router service.
     */
    public void stop() {
        DebugTool.logInfo(TAG, "Stopping transport broker for " + whereToReply);
        synchronized (INIT_LOCK) {
            unregisterWithRouterService();
            unBindFromRouterService();
            routerServiceMessenger = null;
            currentContext = null;

        }
    }

    private void unBindFromRouterService() {
        try {
            getContext().unbindService(routerConnection);

        } catch (Exception e) {
            //This is ok
            DebugTool.logWarning(TAG, "Unable to unbind from router service. bound? " + isBound + " context? " + (getContext()!=null) + " router connection?" + (routerConnection != null));
        }finally {
            isBound = false;
        }
    }

    /***************************************************************************************************************************************
     ***********************************************  Event Callbacks  **************************************************************
     ****************************************************************************************************************************************/


    public void onServiceUnregsiteredFromRouterService(int unregisterCode) {
    }

    @Deprecated
    public void onHardwareDisconnected(TransportType type) {
        stop();
    }

    public void onHardwareDisconnected(TransportRecord record, List<TransportRecord> connectedTransports) {

    }

    /**
     * WILL NO LONGER BE CALLED
     *
     * @param type
     * @return
     */
    @Deprecated
    public boolean onHardwareConnected(TransportType type) {
        synchronized (INIT_LOCK) {
            if (routerServiceMessenger == null) {
                return false;
            }
            return true;
        }
    }

    public boolean onHardwareConnected(List<TransportRecord> transports) {
        synchronized (INIT_LOCK) {
            if (routerServiceMessenger == null && transports != null && transports.size() > 0) {
                return false;
            }
            return true;
        }
    }

    public void onPacketReceived(Parcelable packet) {

    }

    public void onLegacyModeEnabled() {

    }

    protected int getRouterServiceVersion(){
        return routerServiceVersion;
    }

    /**
     * We want to check to see if the Router service is already up and running
     *
     * @param context
     * @return
     */
    private boolean isRouterServiceRunning(Context context) {
        if (context == null) {

            return false;
        }
        ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            //We will check to see if it contains this name, should be pretty specific
            if ((service.service.getClassName()).toLowerCase(Locale.US).contains(SdlBroadcastReceiver.SDL_ROUTER_SERVICE_CLASS_NAME)) {
                this.routerClassName = service.service.getClassName();
                this.routerPackage = service.service.getPackageName();
                return true;
            }
        }
        return false;
    }


    public boolean sendPacketToRouterService(SdlPacket packet) { //We use ints because that is all that is supported by the outputstream class
        //Log.d(TAG,whereToReply + "Sending packet to router service");

        if (routerServiceMessenger == null) {
            DebugTool.logInfo(TAG, whereToReply + " tried to send packet, but no where to send");
            return false;
        }
        if (packet == null
            //|| offset<0
            //|| count<0
                ) {//|| count>(bytes.length-offset)){
            DebugTool.logWarning(TAG, whereToReply + "incorrect params supplied");
            return false;
        }
        byte[] bytes = packet.constructPacket();
        if (bytes.length < ByteArrayMessageSpliter.MAX_BINDER_SIZE) {//Determine if this is under the packet length.
            Message message = Message.obtain(); //Do we need to always obtain new? or can we just swap bundles?
            message.what = TransportConstants.ROUTER_SEND_PACKET;
            Bundle bundle = new Bundle();
            if (routerServiceVersion < TransportConstants.RouterServiceVersions.APPID_STRING) {
                bundle.putLong(TransportConstants.APP_ID_EXTRA, convertAppId(appId));
            }
            bundle.putString(TransportConstants.APP_ID_EXTRA_STRING, appId);
            bundle.putByteArray(TransportConstants.BYTES_TO_SEND_EXTRA_NAME, bytes); //Do we just change this to the args and objs
            bundle.putInt(TransportConstants.BYTES_TO_SEND_EXTRA_OFFSET, 0);
            bundle.putInt(TransportConstants.BYTES_TO_SEND_EXTRA_COUNT, bytes.length);
            bundle.putInt(TransportConstants.BYTES_TO_SEND_FLAGS, TransportConstants.BYTES_TO_SEND_FLAG_NONE);
            bundle.putInt(TransportConstants.PACKET_PRIORITY_COEFFICIENT, packet.getPrioirtyCoefficient());
            if (packet.getTransportRecord() != null) {
                //Log.d(TAG, "Sending packet on transport " + packet.getTransportType().name());
                TransportRecord record = packet.getTransportRecord();
                bundle.putString(TransportConstants.TRANSPORT_TYPE, record.getType().name());
                bundle.putString(TransportConstants.TRANSPORT_ADDRESS, record.getAddress());
            } else {
                //Log.d(TAG, "No transport to be found");
            }
            message.setData(bundle);

            sendMessageToRouterService(message);
            return true;
        } else { //Message is too big for IPC transaction
            //Log.w(TAG, "Message too big for single IPC transaction. Breaking apart. Size - " +  bytes.length);
            ByteArrayMessageSpliter splitter = new ByteArrayMessageSpliter(appId, TransportConstants.ROUTER_SEND_PACKET, bytes, packet.getPrioirtyCoefficient());
            splitter.setRouterServiceVersion(routerServiceVersion);
            splitter.setTransportRecord(packet.getTransportRecord());
            while (splitter.isActive() && routerServiceMessenger != null) {
                sendMessageToRouterService(splitter.nextMessage());
            }
            return splitter.close();
        }

    }

    /**
     * This registers this service with the router service
     */
    private boolean registerWithRouterService() {
        if (getContext() == null) {
            DebugTool.logError(TAG, "Context set to null, failing out");
            return false;
        }

        if (routerServiceMessenger != null) {
            DebugTool.logWarning(TAG, "Already registered with router service");
            return false;
        }
        //Make sure we know where to bind to
        if (this.routerService == null) {
            if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.O) && !isRouterServiceRunning(getContext())) {//We should be able to ignore this case because of the validation now
                DebugTool.logInfo(TAG, whereToReply + " found no router service. Shutting down.");
                this.onHardwareDisconnected(null);
                return false;
            }
        } else {//We were already told where to bind. This should be the case.
            this.routerClassName = this.routerService.getClassName();
            this.routerPackage = this.routerService.getPackageName();
        }

        if (!sendBindingIntent()) {
            DebugTool.logError(TAG, "Something went wrong while trying to bind with the router service.");
            SdlBroadcastReceiver.queryForConnectedService(currentContext);
            return false;
        }
        return true;

    }

    @SuppressLint("InlinedApi")
    private boolean sendBindingIntent() {
        if(this.isBound){
            DebugTool.logError(TAG, "Already bound");
            return false;
        }
        if (this.routerPackage != null && this.routerClassName != null) {
            DebugTool.logInfo(TAG, "Sending bind request to " + this.routerPackage + " - " + this.routerClassName);
            Intent bindingIntent = new Intent();
            bindingIntent.setClassName(this.routerPackage, this.routerClassName);//This sets an explicit intent
            //Quickly make sure it's just up and running
            getContext().startService(bindingIntent);
            bindingIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_CLIENT);
            return getContext().bindService(bindingIntent, routerConnection, Context.BIND_AUTO_CREATE);
        } else {
            return false;
        }
    }

    private void sendRegistrationMessage() {
        Message msg = Message.obtain();
        msg.what = TransportConstants.ROUTER_REGISTER_CLIENT;
        msg.replyTo = this.clientMessenger;
        Bundle bundle = new Bundle();
        bundle.putLong(TransportConstants.APP_ID_EXTRA, convertAppId(appId)); //We send this no matter what due to us not knowing what router version we are connecting to
        bundle.putString(TransportConstants.APP_ID_EXTRA_STRING, appId);
        bundle.putInt(TransportConstants.ROUTER_MESSAGING_VERSION, messagingVersion);
        msg.setData(bundle);
        sendMessageToRouterService(msg);
    }

    private void unregisterWithRouterService() {
        DebugTool.logInfo(TAG, "Attempting to unregister with Sdl Router Service");
        if (isBound && routerServiceMessenger != null) {
            Message msg = Message.obtain();
            msg.what = TransportConstants.ROUTER_UNREGISTER_CLIENT;
            msg.replyTo = this.clientMessenger; //Including this in case this app isn't actually registered with the router service
            Bundle bundle = new Bundle();
            if (routerServiceVersion < TransportConstants.RouterServiceVersions.APPID_STRING) {
                bundle.putLong(TransportConstants.APP_ID_EXTRA, convertAppId(appId));
            }
            bundle.putString(TransportConstants.APP_ID_EXTRA_STRING, appId);
            msg.setData(bundle);
            sendMessageToRouterService(msg);
        } else {
            DebugTool.logWarning(TAG, "Unable to unregister, not bound to router service");
        }

        routerServiceMessenger = null;
    }

    protected ComponentName getRouterService() {
        return this.routerService;
    }

    /**
     * Since it doesn't always make sense to add another service, use this method to get
     * the appropriate context that the rest of this class is using.
     *
     * @return The currently used context for this class
     */
    private Context getContext() {
        return currentContext;
    }


    public static Long convertAppId(String appId) {
        if (appId == null) {
            return -1L;
        }
        try {
            return Long.valueOf(appId);
        } catch (NumberFormatException e) {
            return -1L;
        }
    }

    /***************************************************************************************************************************************
     ***********************************************  LEGACY  *******************************************************************************
     ****************************************************************************************************************************************/
    /*
     * Due to old implementations of SDL/Applink, old versions can't support multiple sessions per RFCOMM channel.
     * This causes a race condition in the router service where only the first app registered will be able to communicate with the
     * head unit. With this additional code, the router service will:
     * 1) Acknowledge it's connected to an old system
     * 2) d/c its bluetooth
     * 3) Send a message to all clients connected that legacy mode is enabled
     * 4) Each client spins up their own bluetooth RFCOMM listening channel
     * 5) Head unit will need to query apps again
     * 6) HU should then connect to each app by their own RFCOMM channel bypassing the router service
     * 7) When the phone is D/C from the head unit the router service will reset and tell clients legacy mode is now off
     */

    private static boolean legacyModeEnabled = false;
    private static Object LEGACY_LOCK = new Object();

    protected void enableLegacyMode(boolean enable) {
        synchronized (LEGACY_LOCK) {
            legacyModeEnabled = enable;
        }
    }

    protected static boolean isLegacyModeEnabled() {
        synchronized (LEGACY_LOCK) {
            return legacyModeEnabled;
        }

    }

    /***************************************************************************************************************************************
     ****************************************************  LEGACY END ***********************************************************************
     ****************************************************************************************************************************************/

    /**
     * Use this method to let the router service know that you are requesting a new session from the head unit.
     */
    @Deprecated
    public void requestNewSession() {
        requestNewSession(null);
    }

    public void requestNewSession(TransportRecord transportRecord) {
        Message msg = Message.obtain();
        msg.what = TransportConstants.ROUTER_REQUEST_NEW_SESSION;
        msg.replyTo = this.clientMessenger; //Including this in case this app isn't actually registered with the router service
        Bundle bundle = new Bundle();
        if (routerServiceVersion < TransportConstants.RouterServiceVersions.APPID_STRING) {
            bundle.putLong(TransportConstants.APP_ID_EXTRA, convertAppId(appId));
        }
        bundle.putString(TransportConstants.APP_ID_EXTRA_STRING, appId);
        if (transportRecord != null) {
            bundle.putString(TransportConstants.TRANSPORT_TYPE, transportRecord.getType().name());
            bundle.putString(TransportConstants.TRANSPORT_ADDRESS, transportRecord.getAddress());
        }
        msg.setData(bundle);
        this.sendMessageToRouterService(msg);
    }

    /**
     * Request secondary transport and communicate details to router service
     *
     * @param sessionId
     * @param bundle
     */
    public void requestSecondaryTransportConnection(byte sessionId, Bundle bundle) {
        Message msg = Message.obtain();
        msg.what = TransportConstants.ROUTER_REQUEST_SECONDARY_TRANSPORT_CONNECTION;
        msg.replyTo = this.clientMessenger;
        if (bundle == null) {
            bundle = new Bundle();
        }
        bundle.putByte(TransportConstants.SESSION_ID_EXTRA, sessionId);
        msg.setData(bundle);
        this.sendMessageToRouterService(msg);
    }


    public void removeSession(long sessionId) {
        Message msg = Message.obtain();
        msg.what = TransportConstants.ROUTER_REMOVE_SESSION;
        msg.replyTo = this.clientMessenger; //Including this in case this app isn't actually registered with the router service
        Bundle bundle = new Bundle();
        if (routerServiceVersion < TransportConstants.RouterServiceVersions.APPID_STRING) {
            bundle.putLong(TransportConstants.APP_ID_EXTRA, convertAppId(appId));
        }
        bundle.putString(TransportConstants.APP_ID_EXTRA_STRING, appId);
        bundle.putLong(TransportConstants.SESSION_ID_EXTRA, sessionId);
        msg.setData(bundle);
        this.sendMessageToRouterService(msg);
    }
}