Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 1 | /* |
| 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 | |
| 15 | package com.android.server.slice; |
| 16 | |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 17 | import android.app.slice.SliceProvider; |
| 18 | import android.app.slice.SliceSpec; |
| 19 | import android.content.ContentProviderClient; |
| 20 | import android.net.Uri; |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 21 | import android.os.Binder; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 22 | import android.os.Bundle; |
Jason Monk | 9e3b864 | 2018-01-21 20:54:00 -0500 | [diff] [blame] | 23 | import android.os.IBinder; |
| 24 | import android.os.IBinder.DeathRecipient; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 25 | import android.os.RemoteException; |
Jason Monk | e8f8be7 | 2018-01-21 10:10:35 -0500 | [diff] [blame] | 26 | import android.util.ArrayMap; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 27 | import android.util.ArraySet; |
| 28 | import android.util.Log; |
| 29 | |
| 30 | import com.android.internal.annotations.GuardedBy; |
| 31 | import com.android.internal.annotations.VisibleForTesting; |
| 32 | |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 33 | import java.util.Arrays; |
| 34 | import java.util.List; |
| 35 | import java.util.Objects; |
| 36 | |
| 37 | /** |
| 38 | * Manages the state of a pinned slice. |
| 39 | */ |
| 40 | public 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 Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 52 | private final ArrayMap<IBinder, ListenerInfo> mListeners = new ArrayMap<>(); |
Jason Monk | f88d25e | 2018-03-06 20:13:24 -0500 | [diff] [blame] | 53 | private final String mPkg; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 54 | @GuardedBy("mLock") |
| 55 | private SliceSpec[] mSupportedSpecs = null; |
Jason Monk | 9e3b864 | 2018-01-21 20:54:00 -0500 | [diff] [blame] | 56 | |
| 57 | private final DeathRecipient mDeathRecipient = this::handleRecheckListeners; |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 58 | private boolean mSlicePinned; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 59 | |
Jason Monk | f88d25e | 2018-03-06 20:13:24 -0500 | [diff] [blame] | 60 | public PinnedSliceState(SliceManagerService service, Uri uri, String pkg) { |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 61 | mService = service; |
| 62 | mUri = uri; |
Jason Monk | f88d25e | 2018-03-06 20:13:24 -0500 | [diff] [blame] | 63 | mPkg = pkg; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 64 | mLock = mService.getLock(); |
| 65 | } |
| 66 | |
Jason Monk | f88d25e | 2018-03-06 20:13:24 -0500 | [diff] [blame] | 67 | public String getPkg() { |
| 68 | return mPkg; |
| 69 | } |
| 70 | |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 71 | 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 Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 107 | setSlicePinned(false); |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 108 | } |
| 109 | |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 110 | 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 Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 122 | public void pin(String pkg, SliceSpec[] specs, IBinder token) { |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 123 | synchronized (mLock) { |
Jason Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 124 | mListeners.put(token, new ListenerInfo(token, pkg, true, |
| 125 | Binder.getCallingUid(), Binder.getCallingPid())); |
Jason Monk | 9e3b864 | 2018-01-21 20:54:00 -0500 | [diff] [blame] | 126 | try { |
Jason Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 127 | token.linkToDeath(mDeathRecipient, 0); |
Jason Monk | 9e3b864 | 2018-01-21 20:54:00 -0500 | [diff] [blame] | 128 | } catch (RemoteException e) { |
| 129 | } |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 130 | mergeSpecs(specs); |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 131 | setSlicePinned(true); |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 132 | } |
| 133 | } |
| 134 | |
Jason Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 135 | public boolean unpin(String pkg, IBinder token) { |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 136 | synchronized (mLock) { |
Jason Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 137 | token.unlinkToDeath(mDeathRecipient, 0); |
| 138 | mListeners.remove(token); |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 139 | } |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 140 | return !hasPinOrListener(); |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | public boolean isListening() { |
| 144 | synchronized (mLock) { |
| 145 | return !mListeners.isEmpty(); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | @VisibleForTesting |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 150 | public boolean hasPinOrListener() { |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 151 | synchronized (mLock) { |
| 152 | return !mPinnedPkgs.isEmpty() || !mListeners.isEmpty(); |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | ContentProviderClient getClient() { |
| 157 | ContentProviderClient client = |
| 158 | mService.getContext().getContentResolver().acquireContentProviderClient(mUri); |
Jason Monk | 2cf6d64 | 2018-01-29 14:09:36 -0500 | [diff] [blame] | 159 | if (client == null) return null; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 160 | client.setDetectNotResponding(SLICE_TIMEOUT); |
| 161 | return client; |
| 162 | } |
| 163 | |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 164 | private void checkSelfRemove() { |
| 165 | if (!hasPinOrListener()) { |
| 166 | // All the listeners died, remove from pinned state. |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 167 | mService.removePinnedSlice(mUri); |
| 168 | } |
| 169 | } |
| 170 | |
Jason Monk | 9e3b864 | 2018-01-21 20:54:00 -0500 | [diff] [blame] | 171 | private void handleRecheckListeners() { |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 172 | if (!hasPinOrListener()) return; |
Jason Monk | 9e3b864 | 2018-01-21 20:54:00 -0500 | [diff] [blame] | 173 | synchronized (mLock) { |
| 174 | for (int i = mListeners.size() - 1; i >= 0; i--) { |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 175 | ListenerInfo l = mListeners.valueAt(i); |
Jason Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 176 | if (!l.token.isBinderAlive()) { |
Jason Monk | 9e3b864 | 2018-01-21 20:54:00 -0500 | [diff] [blame] | 177 | mListeners.removeAt(i); |
| 178 | } |
| 179 | } |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 180 | checkSelfRemove(); |
Jason Monk | 9e3b864 | 2018-01-21 20:54:00 -0500 | [diff] [blame] | 181 | } |
| 182 | } |
| 183 | |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 184 | private void handleSendPinned() { |
| 185 | try (ContentProviderClient client = getClient()) { |
Jason Monk | 2cf6d64 | 2018-01-29 14:09:36 -0500 | [diff] [blame] | 186 | if (client == null) return; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 187 | 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 Monk | 2cf6d64 | 2018-01-29 14:09:36 -0500 | [diff] [blame] | 199 | if (client == null) return; |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 200 | 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 Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 209 | |
| 210 | private class ListenerInfo { |
| 211 | |
Jason Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 212 | private IBinder token; |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 213 | private String pkg; |
| 214 | private boolean hasPermission; |
| 215 | private int callingUid; |
| 216 | private int callingPid; |
| 217 | |
Jason Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 218 | public ListenerInfo(IBinder token, String pkg, boolean hasPermission, |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 219 | int callingUid, int callingPid) { |
Jason Monk | 38df280 | 2018-02-22 19:28:12 -0500 | [diff] [blame] | 220 | this.token = token; |
Jason Monk | 3a1d2e97 | 2018-01-29 16:58:11 -0500 | [diff] [blame] | 221 | this.pkg = pkg; |
| 222 | this.hasPermission = hasPermission; |
| 223 | this.callingUid = callingUid; |
| 224 | this.callingPid = callingPid; |
| 225 | } |
| 226 | } |
Jason Monk | 74f5e36 | 2017-12-06 08:56:33 -0500 | [diff] [blame] | 227 | } |