Home | History | Annotate | Download | only in audio
      1 /*
      2  * Copyright (C) 2010 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.android.mediaframeworktest.functional.audio;
     18 
     19 import com.android.mediaframeworktest.MediaFrameworkTest;
     20 import com.android.mediaframeworktest.MediaNames;
     21 import android.content.Context;
     22 import android.content.res.AssetFileDescriptor;
     23 import android.media.audiofx.AudioEffect;
     24 import android.media.AudioManager;
     25 import android.media.audiofx.Visualizer;
     26 import android.media.MediaPlayer;
     27 
     28 import android.os.Looper;
     29 import android.test.suitebuilder.annotation.LargeTest;
     30 import android.test.suitebuilder.annotation.MediumTest;
     31 import android.test.suitebuilder.annotation.Suppress;
     32 import android.test.ActivityInstrumentationTestCase2;
     33 import android.util.Log;
     34 
     35 import java.nio.ByteOrder;
     36 import java.nio.ByteBuffer;
     37 import java.util.UUID;
     38 
     39 /**
     40  * Junit / Instrumentation test case for the media AudioTrack api
     41 
     42  */
     43 public class MediaVisualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
     44     private String TAG = "MediaVisualizerTest";
     45     private final static int MIN_CAPTURE_RATE_MAX = 20000;
     46     private final static int MIN_SAMPLING_RATE = 8000000;
     47     private final static int MAX_SAMPLING_RATE = 48000000;
     48     private final static int MIN_CAPTURE_SIZE_MAX = 1024;
     49     private final static int MAX_CAPTURE_SIZE_MIN = 128;
     50     // Implementor UUID for volume controller effect defined in
     51     // frameworks/base/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
     52     private final static UUID VOLUME_EFFECT_UUID =
     53         UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b");
     54 
     55     private Visualizer mVisualizer = null;
     56     private int mSession = -1;
     57     private boolean mInitialized = false;
     58     private Looper mLooper = null;
     59     private final Object lock = new Object();
     60     private byte[] mWaveform = null;
     61     private byte[] mFft = null;
     62     private boolean mCaptureWaveform = false;
     63     private boolean mCaptureFft = false;
     64 
     65     public MediaVisualizerTest() {
     66         super("com.android.mediaframeworktest", MediaFrameworkTest.class);
     67     }
     68 
     69     @Override
     70     protected void setUp() throws Exception {
     71       super.setUp();
     72     }
     73 
     74     @Override
     75     protected void tearDown() throws Exception {
     76         super.tearDown();
     77         releaseVisualizer();
     78     }
     79 
     80     private static void assumeTrue(String message, boolean cond) {
     81         assertTrue("(assume)"+message, cond);
     82     }
     83 
     84     private void log(String testName, String message) {
     85         Log.v(TAG, "["+testName+"] "+message);
     86     }
     87 
     88     private void loge(String testName, String message) {
     89         Log.e(TAG, "["+testName+"] "+message);
     90     }
     91 
     92     //-----------------------------------------------------------------
     93     // VISUALIZER TESTS:
     94     //----------------------------------
     95 
     96 
     97     //-----------------------------------------------------------------
     98     // 0 - constructor
     99     //----------------------------------
    100 
    101     //Test case 0.0: test constructor and release
    102     @LargeTest
    103     public void test0_0ConstructorAndRelease() throws Exception {
    104         boolean result = false;
    105         String msg = "test1_0ConstructorAndRelease()";
    106         Visualizer visualizer = null;
    107          try {
    108             visualizer = new Visualizer(0);
    109             assertNotNull(msg + ": could not create Visualizer", visualizer);
    110             result = true;
    111         } catch (IllegalArgumentException e) {
    112             msg = msg.concat(": Visualizer not found");
    113         } catch (UnsupportedOperationException e) {
    114             msg = msg.concat(": Effect library not loaded");
    115         } finally {
    116             if (visualizer != null) {
    117                 visualizer.release();
    118             }
    119         }
    120         assertTrue(msg, result);
    121     }
    122 
    123 
    124     //-----------------------------------------------------------------
    125     // 1 - get/set parameters
    126     //----------------------------------
    127 
    128     //Test case 1.0: check capture rate and sampling rate
    129     @LargeTest
    130     public void test1_0CaptureRates() throws Exception {
    131         boolean result = false;
    132         String msg = "test1_0CaptureRates()";
    133         getVisualizer(0);
    134         try {
    135             int captureRate = mVisualizer.getMaxCaptureRate();
    136             assertTrue(msg +": insufficient max capture rate",
    137                     captureRate >= MIN_CAPTURE_RATE_MAX);
    138             int samplingRate = mVisualizer.getSamplingRate();
    139             assertTrue(msg +": invalid sampling rate",
    140                     samplingRate >= MIN_SAMPLING_RATE && samplingRate <= MAX_SAMPLING_RATE);
    141             result = true;
    142         } catch (IllegalArgumentException e) {
    143             msg = msg.concat(": Bad parameter value");
    144             loge(msg, "Bad parameter value");
    145         } catch (UnsupportedOperationException e) {
    146             msg = msg.concat(": get parameter() rejected");
    147             loge(msg, "get parameter() rejected");
    148         } catch (IllegalStateException e) {
    149             msg = msg.concat("get parameter() called in wrong state");
    150             loge(msg, "get parameter() called in wrong state");
    151         } finally {
    152             releaseVisualizer();
    153         }
    154         assertTrue(msg, result);
    155     }
    156 
    157     //Test case 1.1: check capture size
    158     @LargeTest
    159     public void test1_1CaptureSize() throws Exception {
    160         boolean result = false;
    161         String msg = "test1_1CaptureSize()";
    162         getVisualizer(0);
    163         try {
    164             int[] range = mVisualizer.getCaptureSizeRange();
    165             assertTrue(msg +": insufficient min capture size",
    166                     range[0] <= MAX_CAPTURE_SIZE_MIN);
    167             assertTrue(msg +": insufficient min capture size",
    168                     range[1] >= MIN_CAPTURE_SIZE_MAX);
    169             mVisualizer.setCaptureSize(range[0]);
    170             assertEquals(msg +": insufficient min capture size",
    171                     range[0], mVisualizer.getCaptureSize());
    172             mVisualizer.setCaptureSize(range[1]);
    173             assertEquals(msg +": insufficient min capture size",
    174                     range[1], mVisualizer.getCaptureSize());
    175             result = true;
    176         } catch (IllegalArgumentException e) {
    177             msg = msg.concat(": Bad parameter value");
    178             loge(msg, "Bad parameter value");
    179         } catch (UnsupportedOperationException e) {
    180             msg = msg.concat(": get parameter() rejected");
    181             loge(msg, "get parameter() rejected");
    182         } catch (IllegalStateException e) {
    183             msg = msg.concat("get parameter() called in wrong state");
    184             loge(msg, "get parameter() called in wrong state");
    185         } finally {
    186             releaseVisualizer();
    187         }
    188         assertTrue(msg, result);
    189     }
    190 
    191     //Test case 1.2: check scaling mode
    192     @LargeTest
    193     public void test1_2ScalingMode() throws Exception {
    194         boolean result = false;
    195         String msg = "test1_2ScalingMode()";
    196         getVisualizer(0);
    197         try {
    198             int res = mVisualizer.setScalingMode(Visualizer.SCALING_MODE_AS_PLAYED);
    199             assertEquals(msg + ": setting SCALING_MODE_AS_PLAYED failed",
    200                     res, Visualizer.SUCCESS);
    201             int mode = mVisualizer.getScalingMode();
    202             assertEquals(msg + ": setting SCALING_MODE_AS_PLAYED didn't stick",
    203                     mode, Visualizer.SCALING_MODE_AS_PLAYED);
    204 
    205             res = mVisualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED);
    206             assertEquals(msg + ": setting SCALING_MODE_NORMALIZED failed",
    207                     res, Visualizer.SUCCESS);
    208             mode = mVisualizer.getScalingMode();
    209             assertEquals(msg + ": setting SCALING_MODE_NORMALIZED didn't stick",
    210                     mode, Visualizer.SCALING_MODE_NORMALIZED);
    211 
    212             result = true;
    213         } catch (IllegalStateException e) {
    214             msg = msg.concat("IllegalStateException");
    215             loge(msg, "set/get parameter() called in wrong state: " + e);
    216         } finally {
    217             releaseVisualizer();
    218         }
    219         assertTrue(msg, result);
    220     }
    221 
    222     //-----------------------------------------------------------------
    223     // 2 - check capture
    224     //----------------------------------
    225 
    226     //Test case 2.0: test capture in polling mode
    227     @LargeTest
    228     public void test2_0PollingCapture() throws Exception {
    229         boolean result = false;
    230         String msg = "test2_0PollingCapture()";
    231         AudioEffect vc = null;
    232         MediaPlayer mp = null;
    233         AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
    234         int ringerMode = am.getRingerMode();
    235         am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
    236         int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    237         am.setStreamVolume(AudioManager.STREAM_MUSIC,
    238                            am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
    239                            0);
    240 
    241         try {
    242             // creating a volume controller on output mix ensures that ro.audio.silent mutes
    243             // audio after the effects and not before
    244             vc = new AudioEffect(
    245                                 AudioEffect.EFFECT_TYPE_NULL,
    246                                 VOLUME_EFFECT_UUID,
    247                                 0,
    248                                 0);
    249             vc.setEnabled(true);
    250 
    251             mp = new MediaPlayer();
    252             mp.setDataSource(MediaNames.SINE_200_1000);
    253             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
    254             getVisualizer(mp.getAudioSessionId());
    255             mVisualizer.setEnabled(true);
    256             // check capture on silence
    257             byte[] data = new byte[mVisualizer.getCaptureSize()];
    258             mVisualizer.getWaveForm(data);
    259             int energy = computeEnergy(data, true);
    260             assertEquals(msg +": getWaveForm reports energy for silence",
    261                     0, energy);
    262             mVisualizer.getFft(data);
    263             energy = computeEnergy(data, false);
    264             assertEquals(msg +": getFft reports energy for silence",
    265                     0, energy);
    266             mp.prepare();
    267             mp.start();
    268             Thread.sleep(500);
    269             // check capture on sound
    270             mVisualizer.getWaveForm(data);
    271             energy = computeEnergy(data, true);
    272             assertTrue(msg +": getWaveForm reads insufficient level",
    273                     energy > 0);
    274             mVisualizer.getFft(data);
    275             energy = computeEnergy(data, false);
    276             assertTrue(msg +": getFft reads insufficient level",
    277                     energy > 0);
    278             result = true;
    279         } catch (IllegalArgumentException e) {
    280             msg = msg.concat(": Bad parameter value");
    281             loge(msg, "Bad parameter value");
    282         } catch (UnsupportedOperationException e) {
    283             msg = msg.concat(": get parameter() rejected");
    284             loge(msg, "get parameter() rejected");
    285         } catch (IllegalStateException e) {
    286             msg = msg.concat("get parameter() called in wrong state");
    287             loge(msg, "get parameter() called in wrong state");
    288         } catch (InterruptedException e) {
    289             loge(msg, "sleep() interrupted");
    290         }
    291         finally {
    292             releaseVisualizer();
    293             if (mp != null) {
    294                 mp.release();
    295             }
    296             if (vc != null) {
    297                 vc.release();
    298             }
    299             am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
    300             am.setRingerMode(ringerMode);
    301         }
    302         assertTrue(msg, result);
    303     }
    304 
    305     //Test case 2.1: test capture with listener
    306     @LargeTest
    307     public void test2_1ListenerCapture() throws Exception {
    308         boolean result = false;
    309         String msg = "test2_1ListenerCapture()";
    310         AudioEffect vc = null;
    311         MediaPlayer mp = null;
    312         AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
    313         int ringerMode = am.getRingerMode();
    314         am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
    315         int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    316         am.setStreamVolume(AudioManager.STREAM_MUSIC,
    317                            am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
    318                            0);
    319 
    320         try {
    321             // creating a volume controller on output mix ensures that ro.audio.silent mutes
    322             // audio after the effects and not before
    323             vc = new AudioEffect(
    324                                 AudioEffect.EFFECT_TYPE_NULL,
    325                                 VOLUME_EFFECT_UUID,
    326                                 0,
    327                                 0);
    328             vc.setEnabled(true);
    329 
    330             mp = new MediaPlayer();
    331             mp.setDataSource(MediaNames.SINE_200_1000);
    332             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
    333 
    334             getVisualizer(mp.getAudioSessionId());
    335             createListenerLooper();
    336             synchronized(lock) {
    337                 try {
    338                     lock.wait(1000);
    339                 } catch(Exception e) {
    340                     Log.e(TAG, "Looper creation: wait was interrupted.");
    341                 }
    342             }
    343             assertTrue(mInitialized);
    344 
    345             mVisualizer.setEnabled(true);
    346 
    347             // check capture on silence
    348             synchronized(lock) {
    349                 try {
    350                     mCaptureWaveform = true;
    351                     lock.wait(1000);
    352                     mCaptureWaveform = false;
    353                 } catch(Exception e) {
    354                     Log.e(TAG, "Capture waveform: wait was interrupted.");
    355                 }
    356             }
    357             assertNotNull(msg +": waveform capture failed", mWaveform);
    358             int energy = computeEnergy(mWaveform, true);
    359             assertEquals(msg +": getWaveForm reports energy for silence",
    360                     0, energy);
    361 
    362             synchronized(lock) {
    363                 try {
    364                     mCaptureFft = true;
    365                     lock.wait(1000);
    366                     mCaptureFft = false;
    367                 } catch(Exception e) {
    368                     Log.e(TAG, "Capture FFT: wait was interrupted.");
    369                 }
    370             }
    371             assertNotNull(msg +": FFT capture failed", mFft);
    372             energy = computeEnergy(mFft, false);
    373             assertEquals(msg +": getFft reports energy for silence",
    374                     0, energy);
    375 
    376             mp.prepare();
    377             mp.start();
    378             Thread.sleep(500);
    379 
    380             // check capture on sound
    381             synchronized(lock) {
    382                 try {
    383                     mCaptureWaveform = true;
    384                     lock.wait(1000);
    385                     mCaptureWaveform = false;
    386                 } catch(Exception e) {
    387                     Log.e(TAG, "Capture waveform: wait was interrupted.");
    388                 }
    389             }
    390             assertNotNull(msg +": waveform capture failed", mWaveform);
    391             energy = computeEnergy(mWaveform, true);
    392             assertTrue(msg +": getWaveForm reads insufficient level",
    393                     energy > 0);
    394 
    395             synchronized(lock) {
    396                 try {
    397                     mCaptureFft = true;
    398                     lock.wait(1000);
    399                     mCaptureFft = false;
    400                 } catch(Exception e) {
    401                     Log.e(TAG, "Capture FFT: wait was interrupted.");
    402                 }
    403             }
    404             assertNotNull(msg +": FFT capture failed", mFft);
    405             energy = computeEnergy(mFft, false);
    406             assertTrue(msg +": getFft reads insufficient level",
    407                     energy > 0);
    408 
    409             result = true;
    410         } catch (IllegalArgumentException e) {
    411             msg = msg.concat(": Bad parameter value");
    412             loge(msg, "Bad parameter value");
    413         } catch (UnsupportedOperationException e) {
    414             msg = msg.concat(": get parameter() rejected");
    415             loge(msg, "get parameter() rejected");
    416         } catch (IllegalStateException e) {
    417             msg = msg.concat("get parameter() called in wrong state");
    418             loge(msg, "get parameter() called in wrong state");
    419         } catch (InterruptedException e) {
    420             loge(msg, "sleep() interrupted");
    421         }
    422         finally {
    423             terminateListenerLooper();
    424             releaseVisualizer();
    425             if (mp != null) {
    426                 mp.release();
    427             }
    428             if (vc != null) {
    429                 vc.release();
    430             }
    431             am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
    432             am.setRingerMode(ringerMode);
    433         }
    434         assertTrue(msg, result);
    435     }
    436 
    437     //Test case 2.2: test capture in polling mode with volume scaling
    438     @LargeTest
    439     public void test2_2PollingCaptureVolumeScaling() throws Exception {
    440         // test that when playing a sound, the energy measured with Visualizer in
    441         //   SCALING_MODE_AS_PLAYED mode decreases when lowering the volume
    442         boolean result = false;
    443         String msg = "test2_2PollingCaptureVolumeScaling()";
    444         AudioEffect vc = null;
    445         MediaPlayer mp = null;
    446         AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
    447         int ringerMode = am.getRingerMode();
    448         am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
    449         final int volMax = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    450         am.setStreamVolume(AudioManager.STREAM_MUSIC, volMax, 0);
    451 
    452         try {
    453             // test setup not related to tested functionality:
    454             // creating a volume controller on output mix ensures that ro.audio.silent mutes
    455             // audio after the effects and not before
    456             vc = new AudioEffect(
    457                                 AudioEffect.EFFECT_TYPE_NULL,
    458                                 VOLUME_EFFECT_UUID,
    459                                 0,
    460                                 0);
    461             vc.setEnabled(true);
    462 
    463             mp = new MediaPlayer();
    464             mp.setDataSource(MediaNames.SINE_200_1000);
    465             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
    466             getVisualizer(mp.getAudioSessionId());
    467 
    468             // verify we successfully set the Visualizer in SCALING_MODE_AS_PLAYED mode
    469             mVisualizer.setScalingMode(Visualizer.SCALING_MODE_AS_PLAYED);
    470             assertTrue(msg + " get volume scaling doesn't return SCALING_MODE_AS_PLAYED",
    471                     mVisualizer.getScalingMode() == Visualizer.SCALING_MODE_AS_PLAYED);
    472             mVisualizer.setEnabled(true);
    473             mp.prepare();
    474             mp.start();
    475             Thread.sleep(500);
    476 
    477             // check capture on sound with music volume at max
    478             byte[] data = new byte[mVisualizer.getCaptureSize()];
    479             mVisualizer.getWaveForm(data);
    480             int energyAtVolMax = computeEnergy(data, true);
    481             assertTrue(msg +": getWaveForm reads insufficient level",
    482                     energyAtVolMax > 0);
    483             log(msg, " engergy at max volume = "+energyAtVolMax);
    484 
    485             // check capture on sound with music volume lowered from max
    486             am.setStreamVolume(AudioManager.STREAM_MUSIC, (volMax * 2) / 3, 0);
    487             Thread.sleep(500);
    488             mVisualizer.getWaveForm(data);
    489             int energyAtLowerVol = computeEnergy(data, true);
    490             assertTrue(msg +": getWaveForm at lower volume reads insufficient level",
    491                     energyAtLowerVol > 0);
    492             log(msg, "energy at lower volume = "+energyAtLowerVol);
    493             assertTrue(msg +": getWaveForm didn't report lower energy when volume decreases",
    494                     energyAtVolMax > energyAtLowerVol);
    495 
    496             result = true;
    497         } catch (IllegalArgumentException e) {
    498             msg = msg.concat(": IllegalArgumentException");
    499             loge(msg, " hit exception " + e);
    500         } catch (UnsupportedOperationException e) {
    501             msg = msg.concat(": UnsupportedOperationException");
    502             loge(msg, " hit exception " + e);
    503         } catch (IllegalStateException e) {
    504             msg = msg.concat("IllegalStateException");
    505             loge(msg, " hit exception " + e);
    506         } catch (InterruptedException e) {
    507             loge(msg, " sleep() interrupted");
    508         }
    509         finally {
    510             releaseVisualizer();
    511             if (mp != null) {
    512                 mp.release();
    513             }
    514             if (vc != null) {
    515                 vc.release();
    516             }
    517             am.setRingerMode(ringerMode);
    518         }
    519         assertTrue(msg, result);
    520     }
    521 
    522     //-----------------------------------------------------------------
    523     // private methods
    524     //----------------------------------
    525 
    526     private int computeEnergy(byte[] data, boolean unsigned) {
    527         int energy = 0;
    528         if (data.length != 0) {
    529             for (int i = 0; i < data.length; i++) {
    530                 int tmp;
    531                 // convert from unsigned 8 bit to signed 16 bit
    532                 if (unsigned) {
    533                     tmp = ((int)data[i] & 0xFF) - 128;
    534                 } else {
    535                     tmp = (int)data[i];
    536                 }
    537                 energy += tmp*tmp;
    538             }
    539             energy /= data.length;
    540         }
    541         return energy;
    542     }
    543 
    544     private void getVisualizer(int session) {
    545          if (mVisualizer == null || session != mSession) {
    546              if (session != mSession && mVisualizer != null) {
    547                  mVisualizer.release();
    548                  mVisualizer = null;
    549              }
    550              try {
    551                 mVisualizer = new Visualizer(session);
    552                 mSession = session;
    553             } catch (IllegalArgumentException e) {
    554                 Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e);
    555             } catch (UnsupportedOperationException e) {
    556                 Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e);
    557             }
    558          }
    559          assertNotNull("could not create mVisualizer", mVisualizer);
    560     }
    561 
    562     private void releaseVisualizer() {
    563         if (mVisualizer != null) {
    564             mVisualizer.release();
    565             mVisualizer = null;
    566         }
    567    }
    568 
    569     private void createListenerLooper() {
    570 
    571         new Thread() {
    572             @Override
    573             public void run() {
    574                 // Set up a looper to be used by mEffect.
    575                 Looper.prepare();
    576 
    577                 // Save the looper so that we can terminate this thread
    578                 // after we are done with it.
    579                 mLooper = Looper.myLooper();
    580 
    581                 if (mVisualizer != null) {
    582                     mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
    583                         public void onWaveFormDataCapture(
    584                                 Visualizer visualizer, byte[] waveform, int samplingRate) {
    585                             synchronized(lock) {
    586                                 if (visualizer == mVisualizer) {
    587                                     if (mCaptureWaveform) {
    588                                         mWaveform = waveform;
    589                                         lock.notify();
    590                                     }
    591                                 }
    592                             }
    593                         }
    594 
    595                         public void onFftDataCapture(
    596                                 Visualizer visualizer, byte[] fft, int samplingRate) {
    597                             synchronized(lock) {
    598                                 if (visualizer == mVisualizer) {
    599                                     if (mCaptureFft) {
    600                                         mFft = fft;
    601                                         lock.notify();
    602                                     }
    603                                 }
    604                             }
    605                         }
    606                     },
    607                     10000,
    608                     true,
    609                     true);
    610                 }
    611 
    612                 synchronized(lock) {
    613                     mInitialized = true;
    614                     lock.notify();
    615                 }
    616                 Looper.loop();  // Blocks forever until Looper.quit() is called.
    617             }
    618         }.start();
    619     }
    620     /*
    621      * Terminates the listener looper thread.
    622      */
    623     private void terminateListenerLooper() {
    624         if (mLooper != null) {
    625             mLooper.quit();
    626             mLooper = null;
    627         }
    628     }
    629 
    630 }
    631