blob: 4e7fb969f39830320979ea2de7b7a1142a955d34 [file] [log] [blame]
Jason Monk74f5e362017-12-06 08:56:33 -05001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.server.slice;
16
Jason Monk74f5e362017-12-06 08:56:33 -050017import android.app.slice.SliceProvider;
18import android.app.slice.SliceSpec;
19import android.content.ContentProviderClient;
20import android.net.Uri;
Jason Monk3a1d2e972018-01-29 16:58:11 -050021import android.os.Binder;
Jason Monk74f5e362017-12-06 08:56:33 -050022import android.os.Bundle;
Jason Monk9e3b8642018-01-21 20:54:00 -050023import android.os.IBinder;
24import android.os.IBinder.DeathRecipient;
Jason Monk74f5e362017-12-06 08:56:33 -050025import android.os.RemoteException;
Jason Monke8f8be72018-01-21 10:10:35 -050026import android.util.ArrayMap;
Jason Monk74f5e362017-12-06 08:56:33 -050027import android.util.ArraySet;
28import android.util.Log;
29
30import com.android.internal.annotations.GuardedBy;
31import com.android.internal.annotations.VisibleForTesting;
32
Jason Monk74f5e362017-12-06 08:56:33 -050033import java.util.Arrays;
34import java.util.List;
35import java.util.Objects;
36
37/**
38 * Manages the state of a pinned slice.
39 */
40public class PinnedSliceState {
41
42 private static final long SLICE_TIMEOUT = 5000;
43 private static final String TAG = "PinnedSliceState";
44
45 private final Object mLock;
46
47 private final SliceManagerService mService;
48 private final Uri mUri;
49 @GuardedBy("mLock")
50 private final ArraySet<String> mPinnedPkgs = new ArraySet<>();
51 @GuardedBy("mLock")
Jason Monk3a1d2e972018-01-29 16:58:11 -050052 private final ArrayMap<IBinder, ListenerInfo> mListeners = new ArrayMap<>();
Jason Monkf88d25e2018-03-06 20:13:24 -050053 private final String mPkg;
Jason Monk74f5e362017-12-06 08:56:33 -050054 @GuardedBy("mLock")
55 private SliceSpec[] mSupportedSpecs = null;
Jason Monk9e3b8642018-01-21 20:54:00 -050056
57 private final DeathRecipient mDeathRecipient = this::handleRecheckListeners;
Jason Monk3a1d2e972018-01-29 16:58:11 -050058 private boolean mSlicePinned;
Jason Monk74f5e362017-12-06 08:56:33 -050059
Jason Monkf88d25e2018-03-06 20:13:24 -050060 public PinnedSliceState(SliceManagerService service, Uri uri, String pkg) {
Jason Monk74f5e362017-12-06 08:56:33 -050061 mService = service;
62 mUri = uri;
Jason Monkf88d25e2018-03-06 20:13:24 -050063 mPkg = pkg;
Jason Monk74f5e362017-12-06 08:56:33 -050064 mLock = mService.getLock();
65 }
66
Jason Monkf88d25e2018-03-06 20:13:24 -050067 public String getPkg() {
68 return mPkg;
69 }
70
Jason Monk74f5e362017-12-06 08:56:33 -050071 public SliceSpec[] getSpecs() {
72 return mSupportedSpecs;
73 }
74
75 public void mergeSpecs(SliceSpec[] supportedSpecs) {
76 synchronized (mLock) {
77 if (mSupportedSpecs == null) {
78 mSupportedSpecs = supportedSpecs;
79 } else {
80 List<SliceSpec> specs = Arrays.asList(mSupportedSpecs);
81 mSupportedSpecs = specs.stream().map(s -> {
82 SliceSpec other = findSpec(supportedSpecs, s.getType());
83 if (other == null) return null;
84 if (other.getRevision() < s.getRevision()) {
85 return other;
86 }
87 return s;
88 }).filter(s -> s != null).toArray(SliceSpec[]::new);
89 }
90 }
91 }
92
93 private SliceSpec findSpec(SliceSpec[] specs, String type) {
94 for (SliceSpec spec : specs) {
95 if (Objects.equals(spec.getType(), type)) {
96 return spec;
97 }
98 }
99 return null;
100 }
101
102 public Uri getUri() {
103 return mUri;
104 }
105
106 public void destroy() {
Jason Monk3a1d2e972018-01-29 16:58:11 -0500107 setSlicePinned(false);
Jason Monk74f5e362017-12-06 08:56:33 -0500108 }
109
Jason Monk3a1d2e972018-01-29 16:58:11 -0500110 private void setSlicePinned(boolean pinned) {
111 synchronized (mLock) {
112 if (mSlicePinned == pinned) return;
113 mSlicePinned = pinned;
114 if (pinned) {
115 mService.getHandler().post(this::handleSendPinned);
116 } else {
117 mService.getHandler().post(this::handleSendUnpinned);
118 }
119 }
120 }
121
Jason Monk38df2802018-02-22 19:28:12 -0500122 public void pin(String pkg, SliceSpec[] specs, IBinder token) {
Jason Monk74f5e362017-12-06 08:56:33 -0500123 synchronized (mLock) {
Jason Monk38df2802018-02-22 19:28:12 -0500124 mListeners.put(token, new ListenerInfo(token, pkg, true,
125 Binder.getCallingUid(), Binder.getCallingPid()));
Jason Monk9e3b8642018-01-21 20:54:00 -0500126 try {
Jason Monk38df2802018-02-22 19:28:12 -0500127 token.linkToDeath(mDeathRecipient, 0);
Jason Monk9e3b8642018-01-21 20:54:00 -0500128 } catch (RemoteException e) {
129 }
Jason Monk74f5e362017-12-06 08:56:33 -0500130 mergeSpecs(specs);
Jason Monk3a1d2e972018-01-29 16:58:11 -0500131 setSlicePinned(true);
Jason Monk74f5e362017-12-06 08:56:33 -0500132 }
133 }
134
Jason Monk38df2802018-02-22 19:28:12 -0500135 public boolean unpin(String pkg, IBinder token) {
Jason Monk74f5e362017-12-06 08:56:33 -0500136 synchronized (mLock) {
Jason Monk38df2802018-02-22 19:28:12 -0500137 token.unlinkToDeath(mDeathRecipient, 0);
138 mListeners.remove(token);
Jason Monk74f5e362017-12-06 08:56:33 -0500139 }
Jason Monk3a1d2e972018-01-29 16:58:11 -0500140 return !hasPinOrListener();
Jason Monk74f5e362017-12-06 08:56:33 -0500141 }
142
143 public boolean isListening() {
144 synchronized (mLock) {
145 return !mListeners.isEmpty();
146 }
147 }
148
149 @VisibleForTesting
Jason Monk3a1d2e972018-01-29 16:58:11 -0500150 public boolean hasPinOrListener() {
Jason Monk74f5e362017-12-06 08:56:33 -0500151 synchronized (mLock) {
152 return !mPinnedPkgs.isEmpty() || !mListeners.isEmpty();
153 }
154 }
155
156 ContentProviderClient getClient() {
157 ContentProviderClient client =
158 mService.getContext().getContentResolver().acquireContentProviderClient(mUri);
Jason Monk2cf6d642018-01-29 14:09:36 -0500159 if (client == null) return null;
Jason Monk74f5e362017-12-06 08:56:33 -0500160 client.setDetectNotResponding(SLICE_TIMEOUT);
161 return client;
162 }
163
Jason Monk3a1d2e972018-01-29 16:58:11 -0500164 private void checkSelfRemove() {
165 if (!hasPinOrListener()) {
166 // All the listeners died, remove from pinned state.
Jason Monk3a1d2e972018-01-29 16:58:11 -0500167 mService.removePinnedSlice(mUri);
168 }
169 }
170
Jason Monk9e3b8642018-01-21 20:54:00 -0500171 private void handleRecheckListeners() {
Jason Monk3a1d2e972018-01-29 16:58:11 -0500172 if (!hasPinOrListener()) return;
Jason Monk9e3b8642018-01-21 20:54:00 -0500173 synchronized (mLock) {
174 for (int i = mListeners.size() - 1; i >= 0; i--) {
Jason Monk3a1d2e972018-01-29 16:58:11 -0500175 ListenerInfo l = mListeners.valueAt(i);
Jason Monk38df2802018-02-22 19:28:12 -0500176 if (!l.token.isBinderAlive()) {
Jason Monk9e3b8642018-01-21 20:54:00 -0500177 mListeners.removeAt(i);
178 }
179 }
Jason Monk3a1d2e972018-01-29 16:58:11 -0500180 checkSelfRemove();
Jason Monk9e3b8642018-01-21 20:54:00 -0500181 }
182 }
183
Jason Monk74f5e362017-12-06 08:56:33 -0500184 private void handleSendPinned() {
185 try (ContentProviderClient client = getClient()) {
Jason Monk2cf6d642018-01-29 14:09:36 -0500186 if (client == null) return;
Jason Monk74f5e362017-12-06 08:56:33 -0500187 Bundle b = new Bundle();
188 b.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri);
189 try {
190 client.call(SliceProvider.METHOD_PIN, null, b);
191 } catch (RemoteException e) {
192 Log.w(TAG, "Unable to contact " + mUri, e);
193 }
194 }
195 }
196
197 private void handleSendUnpinned() {
198 try (ContentProviderClient client = getClient()) {
Jason Monk2cf6d642018-01-29 14:09:36 -0500199 if (client == null) return;
Jason Monk74f5e362017-12-06 08:56:33 -0500200 Bundle b = new Bundle();
201 b.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri);
202 try {
203 client.call(SliceProvider.METHOD_UNPIN, null, b);
204 } catch (RemoteException e) {
205 Log.w(TAG, "Unable to contact " + mUri, e);
206 }
207 }
208 }
Jason Monk3a1d2e972018-01-29 16:58:11 -0500209
210 private class ListenerInfo {
211
Jason Monk38df2802018-02-22 19:28:12 -0500212 private IBinder token;
Jason Monk3a1d2e972018-01-29 16:58:11 -0500213 private String pkg;
214 private boolean hasPermission;
215 private int callingUid;
216 private int callingPid;
217
Jason Monk38df2802018-02-22 19:28:12 -0500218 public ListenerInfo(IBinder token, String pkg, boolean hasPermission,
Jason Monk3a1d2e972018-01-29 16:58:11 -0500219 int callingUid, int callingPid) {
Jason Monk38df2802018-02-22 19:28:12 -0500220 this.token = token;
Jason Monk3a1d2e972018-01-29 16:58:11 -0500221 this.pkg = pkg;
222 this.hasPermission = hasPermission;
223 this.callingUid = callingUid;
224 this.callingPid = callingPid;
225 }
226 }
Jason Monk74f5e362017-12-06 08:56:33 -0500227}