IOException: lecture a échoué, socket peut-fermé - Bluetooth sur Android 4.3

actuellement, je suis en train d'essayer de faire face à une Exception étrange lors de l'ouverture D'un BluetoothSocket sur mon Nexus 7 (2012), avec Android 4.3 (construire JWR66Y, je suppose la deuxième mise à jour 4.3). J'ai vu quelques articles connexes (par exemple https://stackoverflow.com/questions/13648373/bluetoothsocket-connect-throwing-exception-read-failed ), mais aucune ne semble apporter de solution à ce problème. En outre, comme suggéré dans ces fils, re-appariement n'aide pas, et en essayant constamment connect (via une boucle stupide) n'a pas non plus d'effet.

Je m'occupe d'un dispositif intégré (un adaptateur de voiture OBD-II Non nom, similaire à http://images04.olx.com/ui/15/53/76/1316534072_254254776_2-OBD-II-BLUTOOTH-ADAPTERSCLEAR-CHECK-ENGINE-LIGHTS-WITH-YOUR-PHONE-Oceanside.jpg ). Mon téléphone Android 2.3.7 n'a aucun problème de connexion, et le Xperia d'un collègue (Android 4.1.2) fonctionne également. Un autre Google Nexus (Je ne sais pas si "un" ou "S", mais non '4') échoue également avec Android 4.3.

Voici l'Extrait de l'établissement de la connexion. Il tourne dans son propre fil, créé au sein d'un Service.

private class ConnectThread extends Thread {

    private static final UUID EMBEDDED_BOARD_SPP = UUID
        .fromString("00001101-0000-1000-8000-00805F9B34FB");

    private BluetoothAdapter adapter;
    private boolean secure;
    private BluetoothDevice device;
    private List<UUID> uuidCandidates;
    private int candidate;
    protected boolean started;

    public ConnectThread(BluetoothDevice device, boolean secure) {
        logger.info("initiliasing connection to device "+device.getName() +" / "+ device.getAddress());
        adapter = BluetoothAdapter.getDefaultAdapter();
        this.secure = secure;
        this.device = device;

        setName("BluetoothConnectThread");

        if (!startQueryingForUUIDs()) {
            this.uuidCandidates = Collections.singletonList(EMBEDDED_BOARD_SPP);
            this.start();
        } else{
            logger.info("Using UUID discovery mechanism.");
        }
        /*
         * it will start upon the broadcast receive otherwise
         */
    }

    private boolean startQueryingForUUIDs() {
        Class<?> cl = BluetoothDevice.class;

        Class<?>[] par = {};
        Method fetchUuidsWithSdpMethod;
        try {
            fetchUuidsWithSdpMethod = cl.getMethod("fetchUuidsWithSdp", par);
        } catch (NoSuchMethodException e) {
            logger.warn(e.getMessage());
            return false;
        }

        Object[] args = {};
        try {
            BroadcastReceiver receiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    BluetoothDevice deviceExtra = intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
                    Parcelable[] uuidExtra = intent.getParcelableArrayExtra("android.bluetooth.device.extra.UUID");

                    uuidCandidates = new ArrayList<UUID>();
                    for (Parcelable uuid : uuidExtra) {
                        uuidCandidates.add(UUID.fromString(uuid.toString()));
                    }

                    synchronized (ConnectThread.this) {
                        if (!ConnectThread.this.started) {
                            ConnectThread.this.start();
                            ConnectThread.this.started = true;
                            unregisterReceiver(this);
                        }

                    }
                }

            };
            registerReceiver(receiver, new IntentFilter("android.bleutooth.device.action.UUID"));
            registerReceiver(receiver, new IntentFilter("android.bluetooth.device.action.UUID"));

            fetchUuidsWithSdpMethod.invoke(device, args);
        } catch (IllegalArgumentException e) {
            logger.warn(e.getMessage());
            return false;
        } catch (IllegalAccessException e) {
            logger.warn(e.getMessage());
            return false;
        } catch (InvocationTargetException e) {
            logger.warn(e.getMessage());
            return false;
        }           

        return true;
    }

    public void run() {
        boolean success = false;
        while (selectSocket()) {

            if (bluetoothSocket == null) {
                logger.warn("Socket is null! Cancelling!");
                deviceDisconnected();
                openTroubleshootingActivity(TroubleshootingActivity.BLUETOOTH_EXCEPTION);
            }

            // Always cancel discovery because it will slow down a connection
            adapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                bluetoothSocket.connect();
                success = true;
                break;

            } catch (IOException e) {
                // Close the socket
                try {
                    shutdownSocket();
                } catch (IOException e2) {
                    logger.warn(e2.getMessage(), e2);
                }
            }
        }

        if (success) {
            deviceConnected();
        } else {
            deviceDisconnected();
            openTroubleshootingActivity(TroubleshootingActivity.BLUETOOTH_EXCEPTION);
        }
    }

    private boolean selectSocket() {
        if (candidate >= uuidCandidates.size()) {
            return false;
        }

        BluetoothSocket tmp;
        UUID uuid = uuidCandidates.get(candidate++);
        logger.info("Attempting to connect to SDP "+ uuid);
        try {
            if (secure) {
                tmp = device.createRfcommSocketToServiceRecord(
                        uuid);
            } else {
                tmp = device.createInsecureRfcommSocketToServiceRecord(
                        uuid);
            }
            bluetoothSocket = tmp;
            return true;
        } catch (IOException e) {
            logger.warn(e.getMessage() ,e);
        }

        return false;
    }

}

le code échoue à bluetoothSocket.connect() . Je reçois un java.io.IOException: read failed, socket might closed, read ret: -1 . C'est la source correspondante à GitHub: https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L504 Son appelé par readInt (), appelé de https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L319

un dump de métadonnées de la socket utilisée a donné les informations suivantes. Ce sont exactement les mêmes sur Nexus 7 et mon téléphone 2.3.7.

Bluetooth Device 'OBDII'
Address: 11:22:33:DD:EE:FF
Bond state: 12 (bonded)
Type: 1
Class major version: 7936
Class minor version: 7936
Class Contents: 0
Contents: 0

j'ai d'autres adaptateurs OBD-II (Plus expansifs) et ils fonctionnent tous. Est-ce qu'il y a une chance que je manque quelque chose ou ça pourrait être un bug dans Androïde?

77
demandé sur Community 2013-09-06 16:03:49

12 réponses

j'ai enfin trouvé une solution. La magie est cachée sous le capot de la classe BluetoothDevice (voir https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothDevice.java#L1037 ).

Maintenant, quand je reçois cette exception, j'instancie un repli BluetoothSocket , similaire au code source ci-dessous. Comme vous pouvez le voir, invoquer la méthode cachée createRfcommSocket via des réflexions. Je n'ai aucune idée pourquoi cette méthode est caché. Le code source le définit comme public cependant...

Class<?> clazz = tmp.getRemoteDevice().getClass();
Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};

Method m = clazz.getMethod("createRfcommSocket", paramTypes);
Object[] params = new Object[] {Integer.valueOf(1)};

fallbackSocket = (BluetoothSocket) m.invoke(tmp.getRemoteDevice(), params);
fallbackSocket.connect();

connect() alors n'échoue plus. J'ai connu quelques problèmes encore. Fondamentalement, cela bloque parfois et échoue. Dans de tels cas, redémarrer le dispositif SPP (plug off / plug in) AIDE. Parfois, j'obtiens aussi une autre requête de couplage après connect() même si le dispositif est déjà lié.

mise à jour:

voici un classe complète, contenant quelques classes imbriquées. pour une mise en œuvre réelle ceux-ci pourraient être tenus comme des classes séparées.

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

public class BluetoothConnector {

    private BluetoothSocketWrapper bluetoothSocket;
    private BluetoothDevice device;
    private boolean secure;
    private BluetoothAdapter adapter;
    private List<UUID> uuidCandidates;
    private int candidate;


    /**
     * @param device the device
     * @param secure if connection should be done via a secure socket
     * @param adapter the Android BT adapter
     * @param uuidCandidates a list of UUIDs. if null or empty, the Serial PP id is used
     */
    public BluetoothConnector(BluetoothDevice device, boolean secure, BluetoothAdapter adapter,
            List<UUID> uuidCandidates) {
        this.device = device;
        this.secure = secure;
        this.adapter = adapter;
        this.uuidCandidates = uuidCandidates;

        if (this.uuidCandidates == null || this.uuidCandidates.isEmpty()) {
            this.uuidCandidates = new ArrayList<UUID>();
            this.uuidCandidates.add(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
        }
    }

    public BluetoothSocketWrapper connect() throws IOException {
        boolean success = false;
        while (selectSocket()) {
            adapter.cancelDiscovery();

            try {
                bluetoothSocket.connect();
                success = true;
                break;
            } catch (IOException e) {
                //try the fallback
                try {
                    bluetoothSocket = new FallbackBluetoothSocket(bluetoothSocket.getUnderlyingSocket());
                    Thread.sleep(500);                  
                    bluetoothSocket.connect();
                    success = true;
                    break;  
                } catch (FallbackException e1) {
                    Log.w("BT", "Could not initialize FallbackBluetoothSocket classes.", e);
                } catch (InterruptedException e1) {
                    Log.w("BT", e1.getMessage(), e1);
                } catch (IOException e1) {
                    Log.w("BT", "Fallback failed. Cancelling.", e1);
                }
            }
        }

        if (!success) {
            throw new IOException("Could not connect to device: "+ device.getAddress());
        }

        return bluetoothSocket;
    }

    private boolean selectSocket() throws IOException {
        if (candidate >= uuidCandidates.size()) {
            return false;
        }

        BluetoothSocket tmp;
        UUID uuid = uuidCandidates.get(candidate++);

        Log.i("BT", "Attempting to connect to Protocol: "+ uuid);
        if (secure) {
            tmp = device.createRfcommSocketToServiceRecord(uuid);
        } else {
            tmp = device.createInsecureRfcommSocketToServiceRecord(uuid);
        }
        bluetoothSocket = new NativeBluetoothSocket(tmp);

        return true;
    }

    public static interface BluetoothSocketWrapper {

        InputStream getInputStream() throws IOException;

        OutputStream getOutputStream() throws IOException;

        String getRemoteDeviceName();

        void connect() throws IOException;

        String getRemoteDeviceAddress();

        void close() throws IOException;

        BluetoothSocket getUnderlyingSocket();

    }


    public static class NativeBluetoothSocket implements BluetoothSocketWrapper {

        private BluetoothSocket socket;

        public NativeBluetoothSocket(BluetoothSocket tmp) {
            this.socket = tmp;
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return socket.getInputStream();
        }

        @Override
        public OutputStream getOutputStream() throws IOException {
            return socket.getOutputStream();
        }

        @Override
        public String getRemoteDeviceName() {
            return socket.getRemoteDevice().getName();
        }

        @Override
        public void connect() throws IOException {
            socket.connect();
        }

        @Override
        public String getRemoteDeviceAddress() {
            return socket.getRemoteDevice().getAddress();
        }

        @Override
        public void close() throws IOException {
            socket.close();
        }

        @Override
        public BluetoothSocket getUnderlyingSocket() {
            return socket;
        }

    }

    public class FallbackBluetoothSocket extends NativeBluetoothSocket {

        private BluetoothSocket fallbackSocket;

        public FallbackBluetoothSocket(BluetoothSocket tmp) throws FallbackException {
            super(tmp);
            try
            {
              Class<?> clazz = tmp.getRemoteDevice().getClass();
              Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};
              Method m = clazz.getMethod("createRfcommSocket", paramTypes);
              Object[] params = new Object[] {Integer.valueOf(1)};
              fallbackSocket = (BluetoothSocket) m.invoke(tmp.getRemoteDevice(), params);
            }
            catch (Exception e)
            {
                throw new FallbackException(e);
            }
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return fallbackSocket.getInputStream();
        }

        @Override
        public OutputStream getOutputStream() throws IOException {
            return fallbackSocket.getOutputStream();
        }


        @Override
        public void connect() throws IOException {
            fallbackSocket.connect();
        }


        @Override
        public void close() throws IOException {
            fallbackSocket.close();
        }

    }

    public static class FallbackException extends Exception {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public FallbackException(Exception e) {
            super(e);
        }

    }
}
115
répondu matthes 2013-12-20 12:47:47

eh bien, j'ai eu le même problème avec mon code, et c'est parce que depuis android 4.2 bluetooth stack a changé. donc mon code fonctionnait très bien sur les appareils avec android < 4.2 , sur les autres appareils j'ai obtenu la célèbre exception "lire échoué, socket pourrait fermé ou le temps mort, lire ret: -1 "

le problème est avec le paramètre socket.mPort . Lorsque vous créez votre socket en utilisant socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID); , le mPort obtient la valeur entière " -1 ", et cette valeur semble ne pas fonctionner pour android > = 4.2 , donc vous devez le définir à " 1 ". La mauvaise nouvelle est que createRfcommSocketToServiceRecord n'accepte que UUID comme paramètre et pas mPort donc nous devons utiliser d'autres aproach. La réponse Postée par @matthes a également fonctionné pour moi, mais je l'ai simplifiée: socket =(BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device,1); . Nous devons utiliser les deux attributs de socket , le second comme un repli.

ainsi le code est (pour connexion à un SPP sur un ELM327 appareil):

BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();

    if (btAdapter.isEnabled()) {
        SharedPreferences prefs_btdev = getSharedPreferences("btdev", 0);
        String btdevaddr=prefs_btdev.getString("btdevaddr","?");

        if (btdevaddr != "?")
        {
            BluetoothDevice device = btAdapter.getRemoteDevice(btdevaddr);

            UUID SERIAL_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); // bluetooth serial port service
            //UUID SERIAL_UUID = device.getUuids()[0].getUuid(); //if you don't know the UUID of the bluetooth device service, you can get it like this from android cache

            BluetoothSocket socket = null;

            try {
                socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID);
            } catch (Exception e) {Log.e("","Error creating socket");}

            try {
                socket.connect();
                Log.e("","Connected");
            } catch (IOException e) {
                Log.e("",e.getMessage());
                try {
                    Log.e("","trying fallback...");

                    socket =(BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device,1);
                    socket.connect();

                    Log.e("","Connected");
                }
             catch (Exception e2) {
                 Log.e("", "Couldn't establish Bluetooth connection!");
              }
            }
        }
        else
        {
            Log.e("","BT device not selected");
        }
    }
79
répondu George Dima 2014-09-03 14:40:05

D'abord, si vous avez besoin de parler à un bluetooth 2.x l'appareil, 151950920" cette documentation stipule que :

indice: si vous vous connectez à une carte série Bluetooth, essayez d'utiliser le bien connu SPP UUID 00001101-0000-1000-8000-00805F9B34FB . Cependant si vous vous connectez à un pair Android alors s'il vous plaît générer votre propre unique UUID.

Je ne pensais pas qu'il fonctionnerait, mais seulement en remplaçant L'UUID par 00001101-0000-1000-8000-00805F9B34FB il fonctionne. Cependant, ce code semble traiter le problème de la version SDK, et vous pouvez simplement remplacer la fonction device.createRfcommSocketToServiceRecord(mMyUuid); par tmp = createBluetoothSocket(mmDevice); après avoir défini la méthode suivante:

private BluetoothSocket createBluetoothSocket(BluetoothDevice device)
    throws IOException {
    if(Build.VERSION.SDK_INT >= 10){
        try {
            final Method m = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", new Class[] { UUID.class });
            return (BluetoothSocket) m.invoke(device, mMyUuid);
        } catch (Exception e) {
            Log.e(TAG, "Could not create Insecure RFComm Connection",e);
        }
    }
    return  device.createRfcommSocketToServiceRecord(mMyUuid);
}

le code source n'est pas le mien, mais vient de ce site web .

9
répondu tobiasBora 2017-01-13 03:55:58

vous avez mis registerReceiver(receiver, new IntentFilter("android.bleutooth.device.action.UUID")); avec "bluetooth" épelé "bleutooth".

6
répondu Fizz Binn 2015-04-24 19:12:27

en fait, j'ai trouvé le problème.

la plupart des gens qui essaient de faire une connexion en utilisant socket.Connect(); obtiennent une exception appelée Java.IO.IOException: read failed, socket might closed, read ret: -1 .

dans certains cas, il dépend également de votre appareil Bluetooth, parce qu'il existe deux types différents de Bluetooth, à savoir blé (basse énergie) et classique.

Si vous voulez vérifier le type de votre appareil Bluetooth est, voici le code:

        String checkType;
        var listDevices = BluetoothAdapter.BondedDevices;
        if (listDevices.Count > 0)
        {
            foreach (var btDevice in listDevices)
            {
                if(btDevice.Name == "MOCUTE-032_B52-CA7E")
                {
                    checkType = btDevice.Type.ToString();
                    Console.WriteLine(checkType);
                }
            }
        }

j'ai essayé pendant des jours pour résoudre le problème, mais depuis aujourd'hui, j'ai trouvé le problème. La solution de @matthes a malheureusement encore quelques problèmes comme il l'a dit déjà, mais voici ma solution.

en ce moment je travaille dans Xamarin Android, mais cela devrait également fonctionner pour d'autres plates-formes.

SOLUTION

S'il y a plus d'un dispositif apparié, alors vous devez supprimer l'autre dispositif apparié appareil. Gardez donc seulement celui que vous voulez connecter (voir la bonne image).

enter image description here enter image description here

dans l'image de gauche, vous voyez que j'ai deux appareils appariés, à savoir" MOCUTE-032_B52-CA7E "et"Blue Easy". C'est le problème, mais je ne sais pas pourquoi ce problème se produit. Peut-être que le Protocole Bluetooth essaye d'obtenir des informations d'un autre Bluetooth appareil.

cependant, le socket.Connect(); fonctionne bien maintenant, sans aucun problème. Donc je voulais juste partager ça, parce que cette erreur est vraiment ennuyante.

bonne chance!

6
répondu Jamie 2016-06-02 17:10:15

j'ai eu les mêmes symptômes que décrit ici. J'ai pu me connecter une fois à une imprimante bluetooth mais les connexions subséquentes ont échoué avec "socket closed" peu importe ce que j'ai fait.

j'ai trouvé un peu étrange que les solutions de rechange décrites ici soient nécessaires. Après avoir passé en revue mon code, j'ai découvert que j'avais oublié de fermer L'entrée et la sortie de la socketsteram et que je n'avais pas terminé les ConnectedThreads correctement.

le fil de connexion que j'utilise est le comme dans l'exemple suivant:

http://developer.android.com/guide/topics/connectivity/bluetooth.html

Notez que ConnectThread et ConnectedThread sont deux classes différentes.

quelle que soit la classe qui démarre le ConnectedThread doit appeler interrompre() et Annuler() sur le thread. J'ai ajouté mmInStream.close () et mmOutStream.close () dans la tête ConnectedTread.la méthode cancel ().

après fermer les threads/streams/sockets correctement je pourrais créer de nouvelles sockets sans aucun problème.

5
répondu Daniel T 2014-12-21 17:15:13

sur les nouvelles versions D'Android, je recevais cette erreur parce que l'adaptateur était encore en train de découvrir quand j'ai essayé de me connecter à la socket. Même si j'ai appelé la méthode cancelDiscovery sur L'adaptateur Bluetooth, j'ai dû attendre jusqu'à ce que le rappel à la méthode Onreceive() de L'émetteur soit appelé avec L'action BluetoothAdapter.ACTION_DISCOVERY_FINISHED.

une fois que j'ai attendu que l'adaptateur arrête la découverte, puis l'appel de connexion sur la prise réussir.

4
répondu kmac.mcfarlane 2016-12-19 20:10:46

dans le cas où quelqu'un a des problèmes avec Kotlin, j'ai dû suivre la réponse acceptée avec quelques variations:

fun print(view: View, text: String) {
    var adapter = BluetoothAdapter.getDefaultAdapter();
    var pairedDevices = adapter.getBondedDevices()
    var uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
    if (pairedDevices.size > 0) {
        for (device in pairedDevices) {
            var s = device.name
            if (device.getName().equals(printerName, ignoreCase = true)) {
                Thread {
                    var socket = device.createInsecureRfcommSocketToServiceRecord(uuid)
                    var clazz = socket.remoteDevice.javaClass
                    var paramTypes = arrayOf<Class<*>>(Integer.TYPE)
                    var m = clazz.getMethod("createRfcommSocket", *paramTypes)
                    var fallbackSocket = m.invoke(socket.remoteDevice, Integer.valueOf(1)) as BluetoothSocket
                    try {
                        fallbackSocket.connect()
                        var stream = fallbackSocket.outputStream
                        stream.write(text.toByteArray(Charset.forName("UTF-8")))
                    } catch (e: Exception) {
                        e.printStackTrace()
                        Snackbar.make(view, "An error occurred", Snackbar.LENGTH_SHORT).show()
                    }
                }.start()
            }
        }
    }
}

j'Espère que ça aide

2
répondu Santiago Martí Olbrich 2018-03-14 18:23:10

j'ai également fait face à ce problème, vous pourriez le résoudre de deux façons , comme mentionné précédemment utiliser la réflexion pour créer le socket Deuxième est, le client est à la recherche d'un serveur avec UUID donné et si votre serveur n'est pas parallèle au client, cela se produit. Créer un serveur avec un UUID client donné, puis écouter et accepter le client du côté du serveur.Ça va marcher.

1
répondu ireshika piyumalie 2016-06-24 09:55:42
Les appareils Bluetooth

peuvent fonctionner à la fois en mode classique et en mode LE. Parfois, ils utilisent une adresse MAC différente en fonction de la façon dont vous vous connectez. Appeler socket.connect() utilise Bluetooth Classic, donc vous devez vous assurer que l'appareil que vous avez obtenu lorsque vous avez scanné était vraiment un appareil classique.

il est facile de filtrer pour seulement les appareils classiques, cependant:

if(BluetoothDevice.DEVICE_TYPE_LE == device.getType()){ //socket.connect() }

sans ce chèque, c'est une course condition à savoir si un scan hybride vous donnera l'appareil classique ou L'appareil BLE en premier. Elle peut apparaître comme une incapacité intermittente à se connecter, ou comme certains appareils pouvant se connecter de manière fiable alors que d'autres apparemment ne le peuvent jamais.

1
répondu kmac.mcfarlane 2017-11-17 16:37:33

même si j'ai eu le même problème ,finalement comprendre mon problème , j'ai essayé de me connecter à partir (hors de portée) gamme de couverture Bluetooth.

0
répondu krishna murthy 2018-06-21 09:32:31

j'ai également reçu le même IOException , mais je trouve la démo du système Android:" BluetoothChat " projet est travaillé. J'ai déterminé que le problème est L'UUID.

donc je remplace mon UUID.fromString("00001001-0000-1000-8000-00805F9B34FB") par UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66") et il a fonctionné la plupart de la scène,seulement parfois besoin de redémarrer le dispositif Bluetooth;

-3
répondu taotao 2014-08-11 14:59:30