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.ActivityManagerNative;
     21 import android.app.AppOpsManager;
     22 import android.app.IActivityManager;
     23 import android.app.assist.AssistContent;
     24 import android.app.assist.AssistStructure;
     25 import android.content.ClipData;
     26 import android.content.ComponentName;
     27 import android.content.ContentProvider;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.ServiceConnection;
     31 import android.graphics.Bitmap;
     32 import android.net.Uri;
     33 import android.os.Binder;
     34 import android.os.Bundle;
     35 import android.os.Handler;
     36 import android.os.IBinder;
     37 import android.os.RemoteException;
     38 import android.os.ServiceManager;
     39 import android.os.UserHandle;
     40 import android.provider.Settings;
     41 import android.service.voice.IVoiceInteractionSession;
     42 import android.service.voice.IVoiceInteractionSessionService;
     43 import android.service.voice.VoiceInteractionService;
     44 import android.service.voice.VoiceInteractionSession;
     45 import android.util.Slog;
     46 import android.view.IWindowManager;
     47 import android.view.WindowManager;
     48 
     49 import com.android.internal.app.AssistUtils;
     50 import com.android.internal.app.IAssistScreenshotReceiver;
     51 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
     52 import com.android.internal.app.IVoiceInteractor;
     53 import com.android.internal.logging.MetricsLogger;
     54 import com.android.internal.os.IResultReceiver;
     55 import com.android.server.LocalServices;
     56 import com.android.server.statusbar.StatusBarManagerInternal;
     57 
     58 import java.io.PrintWriter;
     59 import java.util.ArrayList;
     60 import java.util.List;
     61 
     62 final class VoiceInteractionSessionConnection implements ServiceConnection {
     63 
     64     final static String TAG = "VoiceInteractionServiceManager";
     65 
     66     private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
     67     private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
     68 
     69     final IBinder mToken = new Binder();
     70     final Object mLock;
     71     final ComponentName mSessionComponentName;
     72     final Intent mBindIntent;
     73     final int mUser;
     74     final Context mContext;
     75     final Callback mCallback;
     76     final int mCallingUid;
     77     final Handler mHandler;
     78     final IActivityManager mAm;
     79     final IWindowManager mIWindowManager;
     80     final AppOpsManager mAppOps;
     81     final IBinder mPermissionOwner;
     82     boolean mShown;
     83     Bundle mShowArgs;
     84     int mShowFlags;
     85     boolean mBound;
     86     boolean mFullyBound;
     87     boolean mCanceled;
     88     IVoiceInteractionSessionService mService;
     89     IVoiceInteractionSession mSession;
     90     IVoiceInteractor mInteractor;
     91     boolean mHaveAssistData;
     92     int mPendingAssistDataCount;
     93     ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
     94     boolean mHaveScreenshot;
     95     Bitmap mScreenshot;
     96     ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
     97 
     98     static class AssistDataForActivity {
     99         int activityIndex;
    100         int activityCount;
    101         Bundle data;
    102 
    103         public AssistDataForActivity(Bundle data) {
    104             this.data = data;
    105             Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
    106             if (receiverExtras != null) {
    107                 activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
    108                 activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
    109             }
    110         }
    111     }
    112 
    113     IVoiceInteractionSessionShowCallback mShowCallback =
    114             new IVoiceInteractionSessionShowCallback.Stub() {
    115         @Override
    116         public void onFailed() throws RemoteException {
    117             synchronized (mLock) {
    118                 notifyPendingShowCallbacksFailedLocked();
    119             }
    120         }
    121 
    122         @Override
    123         public void onShown() throws RemoteException {
    124             synchronized (mLock) {
    125                 // TODO: Figure out whether this is good enough or whether we need to hook into
    126                 // Window manager to actually wait for the window to be drawn.
    127                 notifyPendingShowCallbacksShownLocked();
    128             }
    129         }
    130     };
    131 
    132     public interface Callback {
    133         public void sessionConnectionGone(VoiceInteractionSessionConnection connection);
    134         public void onSessionShown(VoiceInteractionSessionConnection connection);
    135         public void onSessionHidden(VoiceInteractionSessionConnection connection);
    136     }
    137 
    138     final ServiceConnection mFullConnection = new ServiceConnection() {
    139         @Override
    140         public void onServiceConnected(ComponentName name, IBinder service) {
    141         }
    142         @Override
    143         public void onServiceDisconnected(ComponentName name) {
    144         }
    145     };
    146 
    147     final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
    148         @Override
    149         public void send(int resultCode, Bundle resultData) throws RemoteException {
    150             synchronized (mLock) {
    151                 if (mShown) {
    152                     mHaveAssistData = true;
    153                     mAssistData.add(new AssistDataForActivity(resultData));
    154                     deliverSessionDataLocked();
    155                 }
    156             }
    157         }
    158     };
    159 
    160     final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
    161         @Override
    162         public void send(Bitmap screenshot) throws RemoteException {
    163             synchronized (mLock) {
    164                 if (mShown) {
    165                     mHaveScreenshot = true;
    166                     mScreenshot = screenshot;
    167                     deliverSessionDataLocked();
    168                 }
    169             }
    170         }
    171     };
    172 
    173     public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
    174             Context context, Callback callback, int callingUid, Handler handler) {
    175         mLock = lock;
    176         mSessionComponentName = component;
    177         mUser = user;
    178         mContext = context;
    179         mCallback = callback;
    180         mCallingUid = callingUid;
    181         mHandler = handler;
    182         mAm = ActivityManagerNative.getDefault();
    183         mIWindowManager = IWindowManager.Stub.asInterface(
    184                 ServiceManager.getService(Context.WINDOW_SERVICE));
    185         mAppOps = context.getSystemService(AppOpsManager.class);
    186         IBinder permOwner = null;
    187         try {
    188             permOwner = mAm.newUriPermissionOwner("voicesession:"
    189                     + component.flattenToShortString());
    190         } catch (RemoteException e) {
    191             Slog.w("voicesession", "AM dead", e);
    192         }
    193         mPermissionOwner = permOwner;
    194         mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
    195         mBindIntent.setComponent(mSessionComponentName);
    196         mBound = mContext.bindServiceAsUser(mBindIntent, this,
    197                 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
    198                         | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
    199         if (mBound) {
    200             try {
    201                 mIWindowManager.addWindowToken(mToken,
    202                         WindowManager.LayoutParams.TYPE_VOICE_INTERACTION);
    203             } catch (RemoteException e) {
    204                 Slog.w(TAG, "Failed adding window token", e);
    205             }
    206         } else {
    207             Slog.w(TAG, "Failed binding to voice interaction session service "
    208                     + mSessionComponentName);
    209         }
    210     }
    211 
    212     public int getUserDisabledShowContextLocked() {
    213         int flags = 0;
    214         if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
    215                 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, mUser) == 0) {
    216             flags |= VoiceInteractionSession.SHOW_WITH_ASSIST;
    217         }
    218         if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
    219                 Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1, mUser) == 0) {
    220             flags |= VoiceInteractionSession.SHOW_WITH_SCREENSHOT;
    221         }
    222         return flags;
    223     }
    224 
    225     public boolean showLocked(Bundle args, int flags, int disabledContext,
    226             IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
    227             List<IBinder> topActivities) {
    228         if (mBound) {
    229             if (!mFullyBound) {
    230                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
    231                         Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY
    232                                 | Context.BIND_FOREGROUND_SERVICE,
    233                         new UserHandle(mUser));
    234             }
    235             mShown = true;
    236             boolean isAssistDataAllowed = true;
    237             try {
    238                 isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
    239             } catch (RemoteException e) {
    240             }
    241             disabledContext |= getUserDisabledShowContextLocked();
    242             boolean structureEnabled = isAssistDataAllowed
    243                     && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
    244             boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
    245                     && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
    246             mShowArgs = args;
    247             mShowFlags = flags;
    248             mHaveAssistData = false;
    249             mPendingAssistDataCount = 0;
    250             boolean needDisclosure = false;
    251             if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
    252                 if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
    253                         mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
    254                         && structureEnabled) {
    255                     mAssistData.clear();
    256                     final int count = activityToken != null ? 1 : topActivities.size();
    257                     // Temp workaround for bug: 28348867  Revert after DP3
    258                     for (int i = 0; i < count && i < 1; 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                 if (mSession != null) {
    457                     try {
    458                         mSession.hide();
    459                     } catch (RemoteException e) {
    460                     }
    461                 }
    462                 try {
    463                     mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
    464                             Intent.FLAG_GRANT_READ_URI_PERMISSION
    465                                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
    466                             mUser);
    467                 } catch (RemoteException e) {
    468                 }
    469                 if (mSession != null) {
    470                     try {
    471                         mAm.finishVoiceTask(mSession);
    472                     } catch (RemoteException e) {
    473                     }
    474                 }
    475                 mCallback.onSessionHidden(this);
    476             }
    477             if (mFullyBound) {
    478                 mContext.unbindService(mFullConnection);
    479                 mFullyBound = false;
    480             }
    481             return true;
    482         }
    483         return false;
    484     }
    485 
    486     public void cancelLocked(boolean finishTask) {
    487         hideLocked();
    488         mCanceled = true;
    489         if (mBound) {
    490             if (mSession != null) {
    491                 try {
    492                     mSession.destroy();
    493                 } catch (RemoteException e) {
    494                     Slog.w(TAG, "Voice interation session already dead");
    495                 }
    496             }
    497             if (finishTask && mSession != null) {
    498                 try {
    499                     mAm.finishVoiceTask(mSession);
    500                 } catch (RemoteException e) {
    501                 }
    502             }
    503             mContext.unbindService(this);
    504             try {
    505                 mIWindowManager.removeWindowToken(mToken);
    506             } catch (RemoteException e) {
    507                 Slog.w(TAG, "Failed removing window token", e);
    508             }
    509             mBound = false;
    510             mService = null;
    511             mSession = null;
    512             mInteractor = null;
    513         }
    514         if (mFullyBound) {
    515             mContext.unbindService(mFullConnection);
    516             mFullyBound = false;
    517         }
    518     }
    519 
    520     public boolean deliverNewSessionLocked(IVoiceInteractionSession session,
    521             IVoiceInteractor interactor) {
    522         mSession = session;
    523         mInteractor = interactor;
    524         if (mShown) {
    525             try {
    526                 session.show(mShowArgs, mShowFlags, mShowCallback);
    527                 mShowArgs = null;
    528                 mShowFlags = 0;
    529             } catch (RemoteException e) {
    530             }
    531             deliverSessionDataLocked();
    532         }
    533         return true;
    534     }
    535 
    536     private void notifyPendingShowCallbacksShownLocked() {
    537         for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
    538             try {
    539                 mPendingShowCallbacks.get(i).onShown();
    540             } catch (RemoteException e) {
    541             }
    542         }
    543         mPendingShowCallbacks.clear();
    544     }
    545 
    546     private void notifyPendingShowCallbacksFailedLocked() {
    547         for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
    548             try {
    549                 mPendingShowCallbacks.get(i).onFailed();
    550             } catch (RemoteException e) {
    551             }
    552         }
    553         mPendingShowCallbacks.clear();
    554     }
    555 
    556     @Override
    557     public void onServiceConnected(ComponentName name, IBinder service) {
    558         synchronized (mLock) {
    559             mService = IVoiceInteractionSessionService.Stub.asInterface(service);
    560             if (!mCanceled) {
    561                 try {
    562                     mService.newSession(mToken, mShowArgs, mShowFlags);
    563                 } catch (RemoteException e) {
    564                     Slog.w(TAG, "Failed adding window token", e);
    565                 }
    566             }
    567         }
    568     }
    569 
    570     @Override
    571     public void onServiceDisconnected(ComponentName name) {
    572         mCallback.sessionConnectionGone(this);
    573         mService = null;
    574     }
    575 
    576     public void dump(String prefix, PrintWriter pw) {
    577         pw.print(prefix); pw.print("mToken="); pw.println(mToken);
    578         pw.print(prefix); pw.print("mShown="); pw.println(mShown);
    579         pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs);
    580         pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags));
    581         pw.print(prefix); pw.print("mBound="); pw.println(mBound);
    582         if (mBound) {
    583             pw.print(prefix); pw.print("mService="); pw.println(mService);
    584             pw.print(prefix); pw.print("mSession="); pw.println(mSession);
    585             pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
    586         }
    587         pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
    588         if (mHaveAssistData) {
    589             pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
    590         }
    591     }
    592 
    593     private Runnable mShowAssistDisclosureRunnable = new Runnable() {
    594         @Override
    595         public void run() {
    596             StatusBarManagerInternal statusBarInternal = LocalServices.getService(
    597                     StatusBarManagerInternal.class);
    598             if (statusBarInternal != null) {
    599                 statusBarInternal.showAssistDisclosure();
    600             }
    601         }
    602     };
    603 };
    604