1 /* 2 * Copyright (C) 2014 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 package android.service.notification; 17 18 import android.annotation.Nullable; 19 import android.annotation.SuppressLint; 20 import android.annotation.TestApi; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.os.SharedMemory; 24 import android.system.ErrnoException; 25 import android.system.OsConstants; 26 27 import androidx.annotation.NonNull; 28 29 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; 30 31 import java.nio.ByteBuffer; 32 33 /** 34 * Represents an update to notification rankings. 35 * @hide 36 */ 37 @SuppressLint({"ParcelNotFinal", "ParcelCreator"}) 38 @TestApi 39 public class NotificationRankingUpdate implements Parcelable { 40 private final NotificationListenerService.RankingMap mRankingMap; 41 42 // The ranking map is stored in shared memory when parceled, for sending across the binder. 43 // This is done because the ranking map can grow large if there are many notifications. 44 private SharedMemory mRankingMapFd = null; 45 private final String mSharedMemoryName = "NotificationRankingUpdatedSharedMemory"; 46 47 /** 48 * @hide 49 */ NotificationRankingUpdate(NotificationListenerService.Ranking[] rankings)50 public NotificationRankingUpdate(NotificationListenerService.Ranking[] rankings) { 51 mRankingMap = new NotificationListenerService.RankingMap(rankings); 52 } 53 54 /** 55 * @hide 56 */ NotificationRankingUpdate(Parcel in)57 public NotificationRankingUpdate(Parcel in) { 58 if (SystemUiSystemPropertiesFlags.getResolver().isEnabled( 59 SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) { 60 // Recover the ranking map from the SharedMemory and store it in mapParcel. 61 final Parcel mapParcel = Parcel.obtain(); 62 ByteBuffer buffer = null; 63 try { 64 // The ranking map should be stored in shared memory when it is parceled, so we 65 // unwrap the SharedMemory object. 66 mRankingMapFd = in.readParcelable(getClass().getClassLoader(), SharedMemory.class); 67 68 // In the case that the ranking map can't be read, readParcelable may return null. 69 // In this case, we set mRankingMap to null; 70 if (mRankingMapFd == null) { 71 mRankingMap = null; 72 return; 73 } 74 // We only need read-only access to the shared memory region. 75 buffer = mRankingMapFd.mapReadOnly(); 76 if (buffer == null) { 77 mRankingMap = null; 78 return; 79 } 80 byte[] payload = new byte[buffer.remaining()]; 81 buffer.get(payload); 82 mapParcel.unmarshall(payload, 0, payload.length); 83 mapParcel.setDataPosition(0); 84 85 mRankingMap = mapParcel.readParcelable(getClass().getClassLoader(), 86 android.service.notification.NotificationListenerService.RankingMap.class); 87 } catch (ErrnoException e) { 88 // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to 89 // avoid crashes; change to Log.wtf. 90 throw new RuntimeException(e); 91 } finally { 92 mapParcel.recycle(); 93 if (buffer != null) { 94 mRankingMapFd.unmap(buffer); 95 mRankingMapFd.close(); 96 } 97 } 98 } else { 99 mRankingMap = in.readParcelable(getClass().getClassLoader(), 100 android.service.notification.NotificationListenerService.RankingMap.class); 101 } 102 } 103 104 /** 105 * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing. 106 * @hide 107 */ 108 @TestApi isFdNotNullAndClosed()109 public final boolean isFdNotNullAndClosed() { 110 return mRankingMapFd != null && mRankingMapFd.getFd() == -1; 111 } 112 113 /** 114 * @hide 115 */ getRankingMap()116 public NotificationListenerService.RankingMap getRankingMap() { 117 return mRankingMap; 118 } 119 120 /** 121 * @hide 122 */ 123 @Override describeContents()124 public int describeContents() { 125 return 0; 126 } 127 128 /** 129 * @hide 130 */ 131 @Override equals(@ullable Object o)132 public boolean equals(@Nullable Object o) { 133 if (this == o) return true; 134 if (o == null || getClass() != o.getClass()) return false; 135 136 NotificationRankingUpdate other = (NotificationRankingUpdate) o; 137 return mRankingMap.equals(other.mRankingMap); 138 } 139 140 /** 141 * @hide 142 */ 143 @Override writeToParcel(@onNull Parcel out, int flags)144 public void writeToParcel(@NonNull Parcel out, int flags) { 145 if (SystemUiSystemPropertiesFlags.getResolver().isEnabled( 146 SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) { 147 final Parcel mapParcel = Parcel.obtain(); 148 try { 149 // Parcels the ranking map and measures its size. 150 mapParcel.writeParcelable(mRankingMap, flags); 151 int mapSize = mapParcel.dataSize(); 152 153 // Creates a new SharedMemory object with enough space to hold the ranking map. 154 SharedMemory mRankingMapFd = SharedMemory.create(mSharedMemoryName, mapSize); 155 if (mRankingMapFd == null) { 156 return; 157 } 158 159 // Gets a read/write buffer mapping the entire shared memory region. 160 final ByteBuffer buffer = mRankingMapFd.mapReadWrite(); 161 162 // Puts the ranking map into the shared memory region buffer. 163 buffer.put(mapParcel.marshall(), 0, mapSize); 164 165 // Protects the region from being written to, by setting it to be read-only. 166 mRankingMapFd.setProtect(OsConstants.PROT_READ); 167 168 // Puts the SharedMemory object in the parcel. 169 out.writeParcelable(mRankingMapFd, flags); 170 } catch (ErrnoException e) { 171 // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to 172 // avoid crashes; change to Log.wtf. 173 throw new RuntimeException(e); 174 } finally { 175 mapParcel.recycle(); 176 } 177 } else { 178 out.writeParcelable(mRankingMap, flags); 179 } 180 } 181 182 /** 183 * @hide 184 */ 185 public static final @android.annotation.NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR 186 = new Parcelable.Creator<NotificationRankingUpdate>() { 187 public NotificationRankingUpdate createFromParcel(Parcel parcel) { 188 return new NotificationRankingUpdate(parcel); 189 } 190 191 public NotificationRankingUpdate[] newArray(int size) { 192 return new NotificationRankingUpdate[size]; 193 } 194 }; 195 } 196