Home | History | Annotate | Download | only in voiceinteraction
      1 /*
      2  * Copyright (C) 2015 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.server.voiceinteraction;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.AppOpsManager;
     21 import android.app.IActivityManager;
     22 import android.app.assist.AssistContent;
     23 import android.app.assist.AssistStructure;
     24 import android.content.ClipData;
     25 import android.content.ComponentName;
     26 import android.content.ContentProvider;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.ServiceConnection;
     30 import android.graphics.Bitmap;
     31 import android.net.Uri;
     32 import android.os.Binder;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.IBinder;
     36 import android.os.RemoteException;
     37 import android.os.ServiceManager;
     38 import android.os.UserHandle;
     39 import android.provider.Settings;
     40 import android.service.voice.IVoiceInteractionSession;
     41 import android.service.voice.IVoiceInteractionSessionService;
     42 import android.service.voice.VoiceInteractionService;
     43 import android.service.voice.VoiceInteractionSession;
     44 import android.util.Slog;
     45 import android.view.IWindowManager;
     46 import android.view.WindowManager;
     47 
     48 import com.android.internal.app.AssistUtils;
     49 import com.android.internal.app.IAssistScreenshotReceiver;
     50 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
     51 import com.android.internal.app.IVoiceInteractor;
     52 import com.android.internal.logging.MetricsLogger;
     53 import com.android.internal.os.IResultReceiver;
     54 import com.android.server.LocalServices;
     55 import com.android.server.statusbar.StatusBarManagerInternal;
     56 
     57 import java.io.PrintWriter;
     58 import java.util.ArrayList;
     59 import java.util.List;
     60 
     61 import static android.view.Display.DEFAULT_DISPLAY;
     62 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
     63 
     64 final class VoiceInteractionSessionConnection implements ServiceConnection {
     65 
     66     final static String TAG = "VoiceInteractionServiceManager";
     67 
     68     private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
     69     private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
     70 
     71     final IBinder mToken = new Binder();
     72     final Object mLock;
     73     final ComponentName mSessionComponentName;
     74     final Intent mBindIntent;
     75     final int mUser;
     76     final Context mContext;
     77     final Callback mCallback;
     78     final int mCallingUid;
     79     final Handler mHandler;
     80     final IActivityManager mAm;
     81     final IWindowManager mIWindowManager;
     82     final AppOpsManager mAppOps;
     83     final IBinder mPermissionOwner;
     84     boolean mShown;
     85     Bundle mShowArgs;
     86     int mShowFlags;
     87     boolean mBound;
     88     boolean mFullyBound;
     89     boolean mCanceled;
     90     IVoiceInteractionSessionService mService;
     91     IVoiceInteractionSession mSession;
     92     IVoiceInteractor mInteractor;
     93     boolean mHaveAssistData;
     94     int mPendingAssistDataCount;
     95     ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
     96     boolean mHaveScreenshot;
     97     Bitmap mScreenshot;
     98     ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
     99 
    100     static class AssistDataForActivity {
    101         int activityIndex;
    102         int activityCount;
    103         Bundle data;
    104 
    105         public AssistDataForActivity(Bundle data) {
    106             this.data = data;
    107             Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
    108             if (receiverExtras != null) {
    109                 activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
    110                 activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
    111             }
    112         }
    113     }
    114 
    115     IVoiceInteractionSessionShowCallback mShowCallback =
    116             new IVoiceInteractionSessionShowCallback.Stub() {
    117         @Override
    118         public void onFailed() throws RemoteException {
    119             synchronized (mLock) {
    120                 notifyPendingShowCallbacksFailedLocked();
    121             }
    122         }
    123 
    124         @Override
    125         public void onShown() throws RemoteException {
    126             synchronized (mLock) {
    127                 // TODO: Figure out whether this is good enough or whether we need to hook into
    128                 // Window manager to actually wait for the window to be drawn.
    129                 notifyPendingShowCallbacksShownLocked();
    130             }
    131         }
    132     };
    133 
    134     public interface Callback {
    135         public void sessionConnectionGone(VoiceInteractionSessionConnection connection);
    136         public void onSessionShown(VoiceInteractionSessionConnection connection);
    137         public void onSessionHidden(VoiceInteractionSessionConnection connection);
    138     }
    139 
    140     final ServiceConnection mFullConnection = new ServiceConnection() {
    141         @Override
    142         public void onServiceConnected(ComponentName name, IBinder service) {
    143         }
    144         @Override
    145         public void onServiceDisconnected(ComponentName name) {
    146         }
    147     };
    148 
    149     final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
    150         @Override
    151         public void send(int resultCode, Bundle resultData) throws RemoteException {
    152             synchronized (mLock) {
    153                 if (mShown) {
    154                     mHaveAssistData = true;
    155                     mAssistData.add(new AssistDataForActivity(resultData));
    156                     deliverSessionDataLocked();
    157                 }
    158             }
    159         }
    160     };
    161 
    162     final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
    163         @Override
    164         public void send(Bitmap screenshot) throws RemoteException {
    165             synchronized (mLock) {
    166                 if (mShown) {
    167                     mHaveScreenshot = true;
    168                     mScreenshot = screenshot;
    169                     deliverSessionDataLocked();
    170                 }
    171             }
    172         }
    173     };
    174 
    175     public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
    176             Context context, Callback callback, int callingUid, Handler handler) {
    177         mLock = lock;
    178         mSessionComponentName = component;
    179         mUser = user;
    180         mContext = context;
    181         mCallback = callback;
    182         mCallingUid = callingUid;
    183         mHandler = handler;
    184         mAm = ActivityManager.getService();
    185         mIWindowManager = IWindowManager.Stub.asInterface(
    186                 ServiceManager.getService(Context.WINDOW_SERVICE));
    187         mAppOps = context.getSystemService(AppOpsManager.class);
    188         IBinder permOwner = null;
    189         try {
    190             permOwner = mAm.newUriPermissionOwner("voicesession:"
    191                     + component.flattenToShortString());
    192         } catch (RemoteException e) {
    193             Slog.w("voicesession", "AM dead", e);
    194         }
    195         mPermissionOwner = permOwner;
    196         mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
    197         mBindIntent.setComponent(mSessionComponentName);
    198         mBound = mContext.bindServiceAsUser(mBindIntent, this,
    199                 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
    200                         | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
    201         if (mBound) {
    202             try {
    203                 mIWindowManager.addWindowToken(mToken, TYPE_VOICE_INTERACTION, DEFAULT_DISPLAY);
    204             } catch (RemoteException e) {
    205                 Slog.w(TAG, "Failed adding window token", e);
    206             }
    207         } else {
    208             Slog.w(TAG, "Failed binding to voice interaction session service "
    209                     + mSessionComponentName);
    210         }
    211     }
    212 
    213     public int getUserDisabledShowContextLocked() {
    214         int flags = 0;
    215         if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
    216                 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, mUser) == 0) {
    217             flags |= VoiceInteractionSession.SHOW_WITH_ASSIST;
    218         }
    219         if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
    220                 Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1, mUser) == 0) {
    221             flags |= VoiceInteractionSession.SHOW_WITH_SCREENSHOT;
    222         }
    223         return flags;
    224     }
    225 
    226     public boolean showLocked(Bundle args, int flags, int disabledContext,
    227             IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
    228             List<IBinder> topActivities) {
    229         if (mBound) {
    230             if (!mFullyBound) {
    231                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
    232                         Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY
    233                                 | Context.BIND_FOREGROUND_SERVICE,
    234                         new UserHandle(mUser));
    235             }
    236             mShown = true;
    237             boolean isAssistDataAllowed = true;
    238             try {
    239                 isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
    240             } catch (RemoteException e) {
    241             }
    242             disabledContext |= getUserDisabledShowContextLocked();
    243             boolean structureEnabled = isAssistDataAllowed
    244                     && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
    245             boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
    246                     && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
    247             mShowArgs = args;
    248             mShowFlags = flags;
    249             mHaveAssistData = false;
    250             mPendingAssistDataCount = 0;
    251             boolean needDisclosure = false;
    252             if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
    253                 if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
    254                         mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
    255                         && structureEnabled) {
    256                     mAssistData.clear();
    257                     final int count = activityToken != null ? 1 : topActivities.size();
    258                     for (int i = 0; i < count; i++) {
    259                         IBinder topActivity = count == 1 ? activityToken : topActivities.get(i);
    260                         try {
    261                             MetricsLogger.count(mContext, "assist_with_context", 1);
    262                             Bundle receiverExtras = new Bundle();
    263                             receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
    264                             receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, count);
    265                             if (mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
    266                                     mAssistReceiver, receiverExtras, topActivity,
    267                                     /* focused= */ i == 0, /* newSessionId= */ i == 0)) {
    268                                 needDisclosure = true;
    269                                 mPendingAssistDataCount++;
    270                             } else if (i == 0) {
    271                                 // Wasn't allowed... given that, let's not do the screenshot either.
    272                                 mHaveAssistData = true;
    273                                 mAssistData.clear();
    274                                 screenshotEnabled = false;
    275                                 break;
    276                             }
    277                         } catch (RemoteException e) {
    278                         }
    279                     }
    280                 } else {
    281                     mHaveAssistData = true;
    282                     mAssistData.clear();
    283                 }
    284             } else {
    285                 mAssistData.clear();
    286             }
    287             mHaveScreenshot = false;
    288             if ((flags&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
    289                 if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
    290                         mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
    291                         && screenshotEnabled) {
    292                     try {
    293                         MetricsLogger.count(mContext, "assist_with_screen", 1);
    294                         needDisclosure = true;
    295                         mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
    296                     } catch (RemoteException e) {
    297                     }
    298                 } else {
    299                     mHaveScreenshot = true;
    300                     mScreenshot = null;
    301                 }
    302             } else {
    303                 mScreenshot = null;
    304             }
    305             if (needDisclosure && AssistUtils.shouldDisclose(mContext, mSessionComponentName)) {
    306                 mHandler.post(mShowAssistDisclosureRunnable);
    307             }
    308             if (mSession != null) {
    309                 try {
    310                     mSession.show(mShowArgs, mShowFlags, showCallback);
    311                     mShowArgs = null;
    312                     mShowFlags = 0;
    313                 } catch (RemoteException e) {
    314                 }
    315                 deliverSessionDataLocked();
    316             } else if (showCallback != null) {
    317                 mPendingShowCallbacks.add(showCallback);
    318             }
    319             mCallback.onSessionShown(this);
    320             return true;
    321         }
    322         if (showCallback != null) {
    323             try {
    324                 showCallback.onFailed();
    325             } catch (RemoteException e) {
    326             }
    327         }
    328         return false;
    329     }
    330 
    331     void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
    332         if (!"content".equals(uri.getScheme())) {
    333             return;
    334         }
    335         long ident = Binder.clearCallingIdentity();
    336         try {
    337             // This will throw SecurityException for us.
    338             mAm.checkGrantUriPermission(srcUid, null, ContentProvider.getUriWithoutUserId(uri),
    339                     mode, ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(srcUid)));
    340             // No security exception, do the grant.
    341             int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
    342             uri = ContentProvider.getUriWithoutUserId(uri);
    343             mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
    344                     uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
    345         } catch (RemoteException e) {
    346         } catch (SecurityException e) {
    347             Slog.w(TAG, "Can't propagate permission", e);
    348         } finally {
    349             Binder.restoreCallingIdentity(ident);
    350         }
    351 
    352     }
    353 
    354     void grantClipDataItemPermission(ClipData.Item item, int mode, int srcUid, int destUid,
    355             String destPkg) {
    356         if (item.getUri() != null) {
    357             grantUriPermission(item.getUri(), mode, srcUid, destUid, destPkg);
    358         }
    359         Intent intent = item.getIntent();
    360         if (intent != null && intent.getData() != null) {
    361             grantUriPermission(intent.getData(), mode, srcUid, destUid, destPkg);
    362         }
    363     }
    364 
    365     void grantClipDataPermissions(ClipData data, int mode, int srcUid, int destUid,
    366             String destPkg) {
    367         final int N = data.getItemCount();
    368         for (int i=0; i<N; i++) {
    369             grantClipDataItemPermission(data.getItemAt(i), mode, srcUid, destUid, destPkg);
    370         }
    371     }
    372 
    373     void deliverSessionDataLocked() {
    374         if (mSession == null) {
    375             return;
    376         }
    377         if (mHaveAssistData) {
    378             AssistDataForActivity assistData;
    379             if (mAssistData.isEmpty()) {
    380                 // We're not actually going to get any data, deliver some nothing
    381                 try {
    382                     mSession.handleAssist(null, null, null, 0, 0);
    383                 } catch (RemoteException e) {
    384                 }
    385             } else {
    386                 while (!mAssistData.isEmpty()) {
    387                     if (mPendingAssistDataCount <= 0) {
    388                         Slog.e(TAG, "mPendingAssistDataCount is " + mPendingAssistDataCount);
    389                     }
    390                     mPendingAssistDataCount--;
    391                     assistData = mAssistData.remove(0);
    392                     if (assistData.data == null) {
    393                         try {
    394                             mSession.handleAssist(null, null, null, assistData.activityIndex,
    395                                     assistData.activityCount);
    396                         } catch (RemoteException e) {
    397                         }
    398                     } else {
    399                         deliverSessionDataLocked(assistData);
    400                     }
    401                 }
    402             }
    403             if (mPendingAssistDataCount <= 0) {
    404                 mHaveAssistData = false;
    405             } // else, more to come
    406         }
    407         if (mHaveScreenshot) {
    408             try {
    409                 mSession.handleScreenshot(mScreenshot);
    410             } catch (RemoteException e) {
    411             }
    412             mScreenshot = null;
    413             mHaveScreenshot = false;
    414         }
    415     }
    416 
    417     private void deliverSessionDataLocked(AssistDataForActivity assistDataForActivity) {
    418         Bundle assistData = assistDataForActivity.data.getBundle(
    419                 VoiceInteractionSession.KEY_DATA);
    420         AssistStructure structure = assistDataForActivity.data.getParcelable(
    421                 VoiceInteractionSession.KEY_STRUCTURE);
    422         AssistContent content = assistDataForActivity.data.getParcelable(
    423                 VoiceInteractionSession.KEY_CONTENT);
    424         int uid = assistDataForActivity.data.getInt(Intent.EXTRA_ASSIST_UID, -1);
    425         if (uid >= 0 && content != null) {
    426             Intent intent = content.getIntent();
    427             if (intent != null) {
    428                 ClipData data = intent.getClipData();
    429                 if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
    430                     grantClipDataPermissions(data, intent.getFlags(), uid,
    431                             mCallingUid, mSessionComponentName.getPackageName());
    432                 }
    433             }
    434             ClipData data = content.getClipData();
    435             if (data != null) {
    436                 grantClipDataPermissions(data,
    437                         Intent.FLAG_GRANT_READ_URI_PERMISSION,
    438                         uid, mCallingUid, mSessionComponentName.getPackageName());
    439             }
    440         }
    441         try {
    442             mSession.handleAssist(assistData, structure, content,
    443                     assistDataForActivity.activityIndex, assistDataForActivity.activityCount);
    444         } catch (RemoteException e) {
    445         }
    446     }
    447 
    448     public boolean hideLocked() {
    449         if (mBound) {
    450             if (mShown) {
    451                 mShown = false;
    452                 mShowArgs = null;
    453                 mShowFlags = 0;
    454                 mHaveAssistData = false;
    455                 mAssistData.clear();
    456                 mPendingShowCallbacks.clear();
    457                 if (mSession != null) {
    458                     try {
    459                         mSession.hide();
    460                     } catch (RemoteException e) {
    461                     }
    462                 }
    463                 try {
    464                     mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
    465                             Intent.FLAG_GRANT_READ_URI_PERMISSION
    466                                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
    467                             mUser);
    468                 } catch (RemoteException e) {
    469                 }
    470                 if (mSession != null) {
    471                     try {
    472                         mAm.finishVoiceTask(mSession);
    473                     } catch (RemoteException e) {
    474                     }
    475                 }
    476                 mCallback.onSessionHidden(this);
    477             }
    478             if (mFullyBound) {
    479                 mContext.unbindService(mFullConnection);
    480                 mFullyBound = false;
    481             }
    482             return true;
    483         }
    484         return false;
    485     }
    486 
    487     public void cancelLocked(boolean finishTask) {
    488         hideLocked();
    489         mCanceled = true;
    490         if (mBound) {
    491             if (mSession != null) {
    492                 try {
    493                     mSession.destroy();
    494                 } catch (RemoteException e) {
    495                     Slog.w(TAG, "Voice interation session already dead");
    496                 }
    497             }
    498             if (finishTask && mSession != null) {
    499                 try {
    500                     mAm.finishVoiceTask(mSession);
    501                 } catch (RemoteException e) {
    502                 }
    503             }
    504             mContext.unbindService(this);
    505             try {
    506                 mIWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY);
    507             } catch (RemoteException e) {
    508                 Slog.w(TAG, "Failed removing window token", e);
    509             }
    510             mBound = false;
    511             mService = null;
    512             mSession = null;
    513             mInteractor = null;
    514         }
    515         if (mFullyBound) {
    516             mContext.unbindService(mFullConnection);
    517             mFullyBound = false;
    518         }
    519     }
    520 
    521     public boolean deliverNewSessionLocked(IVoiceInteractionSession session,
    522             IVoiceInteractor interactor) {
    523         mSession = session;
    524         mInteractor = interactor;
    525         if (mShown) {
    526             try {
    527                 session.show(mShowArgs, mShowFlags, mShowCallback);
    528                 mShowArgs = null;
    529                 mShowFlags = 0;
    530             } catch (RemoteException e) {
    531             }
    532             deliverSessionDataLocked();
    533         }
    534         return true;
    535     }
    536 
    537     private void notifyPendingShowCallbacksShownLocked() {
    538         for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
    539             try {
    540                 mPendingShowCallbacks.get(i).onShown();
    541             } catch (RemoteException e) {
    542             }
    543         }
    544         mPendingShowCallbacks.clear();
    545     }
    546 
    547     private void notifyPendingShowCallbacksFailedLocked() {
    548         for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
    549             try {
    550                 mPendingShowCallbacks.get(i).onFailed();
    551             } catch (RemoteException e) {
    552             }
    553         }
    554         mPendingShowCallbacks.clear();
    555     }
    556 
    557     @Override
    558     public void onServiceConnected(ComponentName name, IBinder service) {
    559         synchronized (mLock) {
    560             mService = IVoiceInteractionSessionService.Stub.asInterface(service);
    561             if (!mCanceled) {
    562                 try {
    563                     mService.newSession(mToken, mShowArgs, mShowFlags);
    564                 } catch (RemoteException e) {
    565                     Slog.w(TAG, "Failed adding window token", e);
    566                 }
    567             }
    568         }
    569     }
    570 
    571     @Override
    572     public void onServiceDisconnected(ComponentName name) {
    573         mCallback.sessionConnectionGone(this);
    574         synchronized (mLock) {
    575             mService = null;
    576         }
    577     }
    578 
    579     public void dump(String prefix, PrintWriter pw) {
    580         pw.print(prefix); pw.print("mToken="); pw.println(mToken);
    581         pw.print(prefix); pw.print("mShown="); pw.println(mShown);
    582         pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs);
    583         pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags));
    584         pw.print(prefix); pw.print("mBound="); pw.println(mBound);
    585         if (mBound) {
    586             pw.print(prefix); pw.print("mService="); pw.println(mService);
    587             pw.print(prefix); pw.print("mSession="); pw.println(mSession);
    588             pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
    589         }
    590         pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
    591         if (mHaveAssistData) {
    592             pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
    593         }
    594     }
    595 
    596     private Runnable mShowAssistDisclosureRunnable = new Runnable() {
    597         @Override
    598         public void run() {
    599             StatusBarManagerInternal statusBarInternal = LocalServices.getService(
    600                     StatusBarManagerInternal.class);
    601             if (statusBarInternal != null) {
    602                 statusBarInternal.showAssistDisclosure();
    603             }
    604         }
    605     };
    606 };
    607