1/*
2 * Copyright (C) 2016 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
17package android.content.pm.permission;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.content.pm.ApplicationInfo;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.IBinder;
29import android.os.Message;
30import android.os.RemoteCallback;
31import android.os.RemoteException;
32import android.permissionpresenterservice.RuntimePermissionPresenterService;
33import android.util.Log;
34import com.android.internal.annotations.GuardedBy;
35import com.android.internal.os.SomeArgs;
36
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.List;
40
41/**
42 * This class provides information about runtime permissions for a specific
43 * app or all apps. This information is dedicated for presentation purposes
44 * and does not necessarily reflect the individual permissions requested/
45 * granted to an app as the platform may be grouping permissions to improve
46 * presentation and help the user make an informed choice. For example, all
47 * runtime permissions in the same permission group may be presented as a
48 * single permission in the UI.
49 *
50 * @hide
51 */
52public final class RuntimePermissionPresenter {
53    private static final String TAG = "RuntimePermPresenter";
54
55    /**
56     * The key for retrieving the result from the returned bundle.
57     *
58     * @hide
59     */
60    public static final String KEY_RESULT =
61            "android.content.pm.permission.RuntimePermissionPresenter.key.result";
62
63    /**
64     * Listener for delivering a result.
65     */
66    public static abstract class OnResultCallback {
67        /**
68         * The result for {@link #getAppPermissions(String, OnResultCallback, Handler)}.
69         * @param permissions The permissions list.
70         */
71        public void onGetAppPermissions(@NonNull
72                List<RuntimePermissionPresentationInfo> permissions) {
73            /* do nothing - stub */
74        }
75
76        /**
77         * The result for {@link #getAppsUsingPermissions(boolean, List)}.
78         * @param system Whether to return only the system apps or only the non-system ones.
79         * @param apps The apps using runtime permissions.
80         */
81        public void getAppsUsingPermissions(boolean system, @NonNull List<ApplicationInfo> apps) {
82            /* do nothing - stub */
83        }
84    }
85
86    private static final Object sLock = new Object();
87
88    @GuardedBy("sLock")
89    private static RuntimePermissionPresenter sInstance;
90
91    private final RemoteService mRemoteService;
92
93    /**
94     * Gets the singleton runtime permission presenter.
95     *
96     * @param context Context for accessing resources.
97     * @return The singleton instance.
98     */
99    public static RuntimePermissionPresenter getInstance(@NonNull Context context) {
100        synchronized (sLock) {
101            if (sInstance == null) {
102                sInstance = new RuntimePermissionPresenter(context.getApplicationContext());
103            }
104            return sInstance;
105        }
106    }
107
108    private RuntimePermissionPresenter(Context context) {
109        mRemoteService = new RemoteService(context);
110    }
111
112    /**
113     * Gets the runtime permissions for an app.
114     *
115     * @param packageName The package for which to query.
116     * @param callback Callback to receive the result.
117     * @param handler Handler on which to invoke the callback.
118     */
119    public void getAppPermissions(@NonNull String packageName,
120            @NonNull OnResultCallback callback, @Nullable Handler handler) {
121        SomeArgs args = SomeArgs.obtain();
122        args.arg1 = packageName;
123        args.arg2 = callback;
124        args.arg3 = handler;
125        Message message = mRemoteService.obtainMessage(
126                RemoteService.MSG_GET_APP_PERMISSIONS, args);
127        mRemoteService.processMessage(message);
128    }
129
130    /**
131     * Gets the system apps that use runtime permissions. System apps are ones
132     * that are considered system for presentation purposes instead of ones
133     * that are preinstalled on the system image. System apps are ones that
134     * are on the system image, haven't been updated (a.k.a factory apps)
135     * that do not have a launcher icon.
136     *
137     * @param system If true only system apps are returned otherwise only
138     *        non-system ones are returned.
139     * @param callback Callback to receive the result.
140     * @param handler Handler on which to invoke the callback.
141     */
142    public void getAppsUsingPermissions(boolean system, @NonNull OnResultCallback callback,
143            @Nullable Handler handler) {
144        SomeArgs args = SomeArgs.obtain();
145        args.arg1 = callback;
146        args.arg2 = handler;
147        args.argi1 = system ? 1 : 0;
148        Message message = mRemoteService.obtainMessage(
149                RemoteService.MSG_GET_APPS_USING_PERMISSIONS, args);
150        mRemoteService.processMessage(message);
151    }
152
153    private static final class RemoteService
154            extends Handler implements ServiceConnection {
155        private static final long UNBIND_TIMEOUT_MILLIS = 10000;
156
157        public static final int MSG_GET_APP_PERMISSIONS = 1;
158        public static final int MSG_GET_APPS_USING_PERMISSIONS = 2;
159        public static final int MSG_UNBIND = 3;
160
161        private final Object mLock = new Object();
162
163        private final Context mContext;
164
165        @GuardedBy("mLock")
166        private final List<Message> mPendingWork = new ArrayList<>();
167
168        @GuardedBy("mLock")
169        private IRuntimePermissionPresenter mRemoteInstance;
170
171        @GuardedBy("mLock")
172        private boolean mBound;
173
174        public RemoteService(Context context) {
175            super(context.getMainLooper(), null, false);
176            mContext = context;
177        }
178
179        public void processMessage(Message message) {
180            synchronized (mLock) {
181                if (!mBound) {
182                    Intent intent = new Intent(
183                            RuntimePermissionPresenterService.SERVICE_INTERFACE);
184                    intent.setPackage(mContext.getPackageManager()
185                            .getPermissionControllerPackageName());
186                    mBound = mContext.bindService(intent, this,
187                            Context.BIND_AUTO_CREATE);
188                }
189                mPendingWork.add(message);
190                scheduleNextMessageIfNeededLocked();
191            }
192        }
193
194        @Override
195        public void onServiceConnected(ComponentName name, IBinder service) {
196            synchronized (mLock) {
197                mRemoteInstance = IRuntimePermissionPresenter.Stub.asInterface(service);
198                scheduleNextMessageIfNeededLocked();
199            }
200        }
201
202        @Override
203        public void onServiceDisconnected(ComponentName name) {
204            synchronized (mLock) {
205                mRemoteInstance = null;
206            }
207        }
208
209        @Override
210        public void handleMessage(Message msg) {
211            switch (msg.what) {
212                case MSG_GET_APP_PERMISSIONS: {
213                    SomeArgs args = (SomeArgs) msg.obj;
214                    final String packageName = (String) args.arg1;
215                    final OnResultCallback callback = (OnResultCallback) args.arg2;
216                    final Handler handler = (Handler) args.arg3;
217                    args.recycle();
218                    final IRuntimePermissionPresenter remoteInstance;
219                    synchronized (mLock) {
220                        remoteInstance = mRemoteInstance;
221                    }
222                    if (remoteInstance == null) {
223                        return;
224                    }
225                    try {
226                        remoteInstance.getAppPermissions(packageName,
227                                new RemoteCallback(new RemoteCallback.OnResultListener() {
228                            @Override
229                            public void onResult(Bundle result) {
230                                final List<RuntimePermissionPresentationInfo> reportedPermissions;
231                                List<RuntimePermissionPresentationInfo> permissions = null;
232                                if (result != null) {
233                                    permissions = result.getParcelableArrayList(KEY_RESULT);
234                                }
235                                if (permissions == null) {
236                                    permissions = Collections.emptyList();
237                                }
238                                reportedPermissions = permissions;
239                                if (handler != null) {
240                                    handler.post(new Runnable() {
241                                        @Override
242                                        public void run() {
243                                            callback.onGetAppPermissions(reportedPermissions);
244                                        }
245                                    });
246                                } else {
247                                    callback.onGetAppPermissions(reportedPermissions);
248                                }
249                            }
250                        }, this));
251                    } catch (RemoteException re) {
252                        Log.e(TAG, "Error getting app permissions", re);
253                    }
254                    scheduleUnbind();
255                } break;
256
257                case MSG_GET_APPS_USING_PERMISSIONS: {
258                    SomeArgs args = (SomeArgs) msg.obj;
259                    final OnResultCallback callback = (OnResultCallback) args.arg1;
260                    final Handler handler = (Handler) args.arg2;
261                    final boolean system = args.argi1 == 1;
262                    args.recycle();
263                    final IRuntimePermissionPresenter remoteInstance;
264                    synchronized (mLock) {
265                        remoteInstance = mRemoteInstance;
266                    }
267                    if (remoteInstance == null) {
268                        return;
269                    }
270                    try {
271                        remoteInstance.getAppsUsingPermissions(system, new RemoteCallback(
272                                new RemoteCallback.OnResultListener() {
273                            @Override
274                            public void onResult(Bundle result) {
275                                final List<ApplicationInfo> reportedApps;
276                                List<ApplicationInfo> apps = null;
277                                if (result != null) {
278                                    apps = result.getParcelableArrayList(KEY_RESULT);
279                                }
280                                if (apps == null) {
281                                    apps = Collections.emptyList();
282                                }
283                                reportedApps = apps;
284                                if (handler != null) {
285                                    handler.post(new Runnable() {
286                                        @Override
287                                        public void run() {
288                                            callback.getAppsUsingPermissions(system, reportedApps);
289                                        }
290                                    });
291                                } else {
292                                    callback.getAppsUsingPermissions(system, reportedApps);
293                                }
294                            }
295                        }, this));
296                    } catch (RemoteException re) {
297                        Log.e(TAG, "Error getting apps using permissions", re);
298                    }
299                    scheduleUnbind();
300                } break;
301
302                case MSG_UNBIND: {
303                    synchronized (mLock) {
304                        if (mBound) {
305                            mContext.unbindService(this);
306                            mBound = false;
307                        }
308                        mRemoteInstance = null;
309                    }
310                } break;
311            }
312
313            synchronized (mLock) {
314                scheduleNextMessageIfNeededLocked();
315            }
316        }
317
318        private void scheduleNextMessageIfNeededLocked() {
319            if (mBound && mRemoteInstance != null && !mPendingWork.isEmpty()) {
320                Message nextMessage = mPendingWork.remove(0);
321                sendMessage(nextMessage);
322            }
323        }
324
325        private void scheduleUnbind() {
326            removeMessages(MSG_UNBIND);
327            sendEmptyMessageDelayed(MSG_UNBIND, UNBIND_TIMEOUT_MILLIS);
328        }
329    }
330}
331