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 17 import android.app.slice.SliceProvider; 18 import android.app.slice.SliceSpec; 19 import android.content.ContentProviderClient; 20 import android.net.Uri; 21 import android.os.Binder; 22 import android.os.Bundle; 23 import android.os.IBinder; 24 import android.os.IBinder.DeathRecipient; 25 import android.os.RemoteException; 26 import android.util.ArrayMap; 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 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") 52 private final ArrayMap<IBinder, ListenerInfo> mListeners = new ArrayMap<>(); 53 private final String mPkg; 54 @GuardedBy("mLock") 55 private SliceSpec[] mSupportedSpecs = null; 56 57 private final DeathRecipient mDeathRecipient = this::handleRecheckListeners; 58 private boolean mSlicePinned; 59 PinnedSliceState(SliceManagerService service, Uri uri, String pkg)60 public PinnedSliceState(SliceManagerService service, Uri uri, String pkg) { 61 mService = service; 62 mUri = uri; 63 mPkg = pkg; 64 mLock = mService.getLock(); 65 } 66 getPkg()67 public String getPkg() { 68 return mPkg; 69 } 70 getSpecs()71 public SliceSpec[] getSpecs() { 72 return mSupportedSpecs; 73 } 74 mergeSpecs(SliceSpec[] supportedSpecs)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 findSpec(SliceSpec[] specs, String type)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 getUri()102 public Uri getUri() { 103 return mUri; 104 } 105 destroy()106 public void destroy() { 107 setSlicePinned(false); 108 } 109 setSlicePinned(boolean pinned)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 pin(String pkg, SliceSpec[] specs, IBinder token)122 public void pin(String pkg, SliceSpec[] specs, IBinder token) { 123 synchronized (mLock) { 124 mListeners.put(token, new ListenerInfo(token, pkg, true, 125 Binder.getCallingUid(), Binder.getCallingPid())); 126 try { 127 token.linkToDeath(mDeathRecipient, 0); 128 } catch (RemoteException e) { 129 } 130 mergeSpecs(specs); 131 setSlicePinned(true); 132 } 133 } 134 unpin(String pkg, IBinder token)135 public boolean unpin(String pkg, IBinder token) { 136 synchronized (mLock) { 137 token.unlinkToDeath(mDeathRecipient, 0); 138 mListeners.remove(token); 139 } 140 return !hasPinOrListener(); 141 } 142 isListening()143 public boolean isListening() { 144 synchronized (mLock) { 145 return !mListeners.isEmpty(); 146 } 147 } 148 149 @VisibleForTesting hasPinOrListener()150 public boolean hasPinOrListener() { 151 synchronized (mLock) { 152 return !mPinnedPkgs.isEmpty() || !mListeners.isEmpty(); 153 } 154 } 155 getClient()156 ContentProviderClient getClient() { 157 ContentProviderClient client = mService.getContext().getContentResolver() 158 .acquireUnstableContentProviderClient(mUri); 159 if (client == null) return null; 160 client.setDetectNotResponding(SLICE_TIMEOUT); 161 return client; 162 } 163 checkSelfRemove()164 private void checkSelfRemove() { 165 if (!hasPinOrListener()) { 166 // All the listeners died, remove from pinned state. 167 mService.removePinnedSlice(mUri); 168 } 169 } 170 handleRecheckListeners()171 private void handleRecheckListeners() { 172 if (!hasPinOrListener()) return; 173 synchronized (mLock) { 174 for (int i = mListeners.size() - 1; i >= 0; i--) { 175 ListenerInfo l = mListeners.valueAt(i); 176 if (!l.token.isBinderAlive()) { 177 mListeners.removeAt(i); 178 } 179 } 180 checkSelfRemove(); 181 } 182 } 183 handleSendPinned()184 private void handleSendPinned() { 185 try (ContentProviderClient client = getClient()) { 186 if (client == null) return; 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 (Exception e) { 192 Log.w(TAG, "Unable to contact " + mUri, e); 193 } 194 } 195 } 196 handleSendUnpinned()197 private void handleSendUnpinned() { 198 try (ContentProviderClient client = getClient()) { 199 if (client == null) return; 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 (Exception e) { 205 Log.w(TAG, "Unable to contact " + mUri, e); 206 } 207 } 208 } 209 210 private class ListenerInfo { 211 212 private IBinder token; 213 private String pkg; 214 private boolean hasPermission; 215 private int callingUid; 216 private int callingPid; 217 ListenerInfo(IBinder token, String pkg, boolean hasPermission, int callingUid, int callingPid)218 public ListenerInfo(IBinder token, String pkg, boolean hasPermission, 219 int callingUid, int callingPid) { 220 this.token = token; 221 this.pkg = pkg; 222 this.hasPermission = hasPermission; 223 this.callingUid = callingUid; 224 this.callingPid = callingPid; 225 } 226 } 227 } 228