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