1/*
2 * Copyright (C) 2017 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 */
16package android.car.storagemonitoring;
17
18import android.annotation.RequiresPermission;
19import android.annotation.SystemApi;
20import android.car.Car;
21import android.car.CarManagerBase;
22import android.car.CarNotConnectedException;
23import android.os.Handler;
24import android.os.IBinder;
25import android.os.RemoteException;
26import com.android.car.internal.SingleMessageHandler;
27import java.lang.ref.WeakReference;
28import java.util.Collections;
29import java.util.HashSet;
30import java.util.List;
31import java.util.Set;
32
33import static android.car.CarApiUtil.checkCarNotConnectedExceptionFromCarService;
34
35/**
36 * API for retrieving information and metrics about the flash storage.
37 *
38 * @hide
39 */
40@SystemApi
41public final class CarStorageMonitoringManager implements CarManagerBase {
42    private static final String TAG = CarStorageMonitoringManager.class.getSimpleName();
43    private static final int MSG_IO_STATS_EVENT = 0;
44
45    private final ICarStorageMonitoring mService;
46    private ListenerToService mListenerToService;
47    private final SingleMessageHandler<IoStats> mMessageHandler;
48    private final Set<IoStatsListener> mListeners = new HashSet<>();
49
50    public interface IoStatsListener {
51        void onSnapshot(IoStats snapshot);
52    }
53    private static final class ListenerToService extends IIoStatsListener.Stub {
54        private final WeakReference<CarStorageMonitoringManager> mManager;
55
56        ListenerToService(CarStorageMonitoringManager manager) {
57            mManager = new WeakReference<>(manager);
58        }
59
60        @Override
61        public void onSnapshot(IoStats snapshot) {
62            CarStorageMonitoringManager manager = mManager.get();
63            if (manager != null) {
64                manager.mMessageHandler.sendEvents(Collections.singletonList(snapshot));
65            }
66        }
67    }
68
69    public static final String INTENT_EXCESSIVE_IO = "android.car.storagemonitoring.EXCESSIVE_IO";
70
71    public static final int PRE_EOL_INFO_UNKNOWN = 0;
72    public static final int PRE_EOL_INFO_NORMAL = 1;
73    public static final int PRE_EOL_INFO_WARNING = 2;
74    public static final int PRE_EOL_INFO_URGENT = 3;
75
76    public static final long SHUTDOWN_COST_INFO_MISSING = -1;
77
78    /**
79     * @hide
80     */
81    public CarStorageMonitoringManager(IBinder service, Handler handler) {
82        mService = ICarStorageMonitoring.Stub.asInterface(service);
83        mMessageHandler = new SingleMessageHandler<IoStats>(handler, MSG_IO_STATS_EVENT) {
84            @Override
85            protected void handleEvent(IoStats event) {
86                for (IoStatsListener listener : mListeners) {
87                    listener.onSnapshot(event);
88                }
89            }
90        };
91    }
92
93    /**
94     * @hide
95     */
96    @Override
97    public void onCarDisconnected() {
98        mListeners.clear();
99        mListenerToService = null;
100    }
101
102    // ICarStorageMonitoring forwards
103
104    /**
105     * This method returns the value of the "pre EOL" indicator for the flash storage
106     * as retrieved during the current boot cycle.
107     *
108     * It will return either PRE_EOL_INFO_UNKNOWN if the value can't be determined,
109     * or one of PRE_EOL_INFO_{NORMAL|WARNING|URGENT} depending on the device state.
110     */
111    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
112    public int getPreEolIndicatorStatus() throws CarNotConnectedException {
113        try {
114            return mService.getPreEolIndicatorStatus();
115        } catch (IllegalStateException e) {
116            checkCarNotConnectedExceptionFromCarService(e);
117        } catch (RemoteException e) {
118            throw new CarNotConnectedException();
119        }
120        return PRE_EOL_INFO_UNKNOWN;
121    }
122
123    /**
124     * This method returns the value of the wear estimate indicators for the flash storage
125     * as retrieved during the current boot cycle.
126     *
127     * The indicators are guaranteed to be a lower-bound on the actual wear of the storage.
128     * Current technology in common automotive usage offers estimates in 10% increments.
129     *
130     * If either or both indicators are not available, they will be reported as UNKNOWN.
131     */
132    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
133    public WearEstimate getWearEstimate() throws CarNotConnectedException {
134        try {
135            return mService.getWearEstimate();
136        } catch (IllegalStateException e) {
137            checkCarNotConnectedExceptionFromCarService(e);
138        } catch (RemoteException e) {
139            throw new CarNotConnectedException();
140        }
141        return WearEstimate.UNKNOWN_ESTIMATE;
142    }
143
144    /**
145     * This method returns a list of all changes in wear estimate indicators detected during the
146     * lifetime of the system.
147     *
148     * The indicators are not guaranteed to persist across a factory reset.
149     *
150     * The indicators are guaranteed to be a lower-bound on the actual wear of the storage.
151     * Current technology in common automotive usage offers estimates in 10% increments.
152     *
153     * If no indicators are available, an empty list will be returned.
154     */
155    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
156    public List<WearEstimateChange> getWearEstimateHistory() throws CarNotConnectedException {
157        try {
158            return mService.getWearEstimateHistory();
159        } catch (IllegalStateException e) {
160            checkCarNotConnectedExceptionFromCarService(e);
161        } catch (RemoteException e) {
162            throw new CarNotConnectedException();
163        }
164        return Collections.emptyList();
165    }
166
167    /**
168     * This method returns a list of per user-id I/O activity metrics as collected at the end of
169     * system boot.
170     *
171     * The BOOT_COMPLETE broadcast is used as the trigger to collect this data. The implementation
172     * may impose an additional, and even variable across boot cycles, delay between the sending
173     * of the broadcast and the collection of the data.
174     *
175     * If the information is not available, an empty list will be returned.
176     */
177    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
178    public List<IoStatsEntry> getBootIoStats() throws CarNotConnectedException {
179        try {
180            return mService.getBootIoStats();
181        } catch (IllegalStateException e) {
182            checkCarNotConnectedExceptionFromCarService(e);
183        } catch (RemoteException e) {
184            throw new CarNotConnectedException();
185        }
186        return Collections.emptyList();
187    }
188
189    /**
190     * This method returns an approximation of the number of bytes written to disk during
191     * the course of the previous system shutdown.
192     *
193     * <p>For purposes of this API the system shutdown is defined as starting when CarService
194     * receives the ACTION_SHUTDOWN or ACTION_REBOOT intent from the system.</p>
195     *
196     * <p>The information provided by this API does not provide attribution of the disk writes to
197     * specific applications or system daemons.</p>
198     *
199     * <p>The information returned by this call is a best effort guess, whose accuracy depends
200     * on the underlying file systems' ability to reliably track and accumulate
201     * disk write sizes.</p>
202     *
203     * <p>A corrupt file system, or one which was not cleanly unmounted during shutdown, may
204     * be unable to provide any information, or may provide incorrect data. While the API
205     * will attempt to detect these scenarios, the detection may fail and incorrect data
206     * may end up being used in calculations.</p>
207     *
208     * <p>If the information is not available, SHUTDOWN_COST_INFO_MISSING will be returned.</p>s
209     */
210    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
211    public long getShutdownDiskWriteAmount() throws CarNotConnectedException {
212        try {
213            return mService.getShutdownDiskWriteAmount();
214        } catch (IllegalStateException e) {
215            checkCarNotConnectedExceptionFromCarService(e);
216        } catch (RemoteException e) {
217            throw new CarNotConnectedException();
218        }
219        return SHUTDOWN_COST_INFO_MISSING;
220    }
221
222    /**
223     * This method returns a list of per user-id I/O activity metrics as collected from kernel
224     * start until the last snapshot.
225     *
226     * The samples provided might be as old as the value of the ioStatsRefreshRateSeconds setting.
227     *
228     * If the information is not available, an empty list will be returned.
229     */
230    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
231    public List<IoStatsEntry> getAggregateIoStats() throws CarNotConnectedException {
232        try {
233            return mService.getAggregateIoStats();
234        } catch (IllegalStateException e) {
235            checkCarNotConnectedExceptionFromCarService(e);
236        } catch (RemoteException e) {
237            throw new CarNotConnectedException();
238        }
239        return Collections.emptyList();
240    }
241
242    /**
243     * This method returns a list of the I/O stats deltas currently stored by the system.
244     *
245     * Periodically, the system gathers I/O activity metrics and computes and stores a delta from
246     * the previous cycle. The timing and the number of these stored samples are configurable
247     * by the OEM.
248     *
249     * The samples are returned in order from the oldest to the newest.
250     *
251     * If the information is not available, an empty list will be returned.
252     */
253    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
254    public List<IoStats> getIoStatsDeltas() throws CarNotConnectedException {
255        try {
256            return mService.getIoStatsDeltas();
257        } catch (IllegalStateException e) {
258            checkCarNotConnectedExceptionFromCarService(e);
259        } catch (RemoteException e) {
260            throw new CarNotConnectedException();
261        }
262        return Collections.emptyList();
263    }
264
265    /**
266     * This method registers a new listener to receive I/O stats deltas.
267     *
268     * The system periodically gathers I/O activity metrics and computes a delta of such
269     * activity. Registered listeners will receive those deltas as they are available.
270     *
271     * The timing of availability of the deltas is configurable by the OEM.
272     */
273    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
274    public void registerListener(IoStatsListener listener) throws CarNotConnectedException {
275        try {
276            if (mListeners.isEmpty()) {
277                if (mListenerToService == null) {
278                    mListenerToService = new ListenerToService(this);
279                }
280                mService.registerListener(mListenerToService);
281            }
282            mListeners.add(listener);
283        } catch (IllegalStateException e) {
284            checkCarNotConnectedExceptionFromCarService(e);
285        } catch (RemoteException e) {
286            throw new CarNotConnectedException();
287        }
288    }
289
290    /**
291     * This method removes a registered listener of I/O stats deltas.
292     */
293    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
294    public void unregisterListener(IoStatsListener listener) throws CarNotConnectedException {
295        try {
296            if (!mListeners.remove(listener)) {
297                return;
298            }
299            if (mListeners.isEmpty()) {
300                mService.unregisterListener(mListenerToService);
301                mListenerToService = null;
302            }
303        } catch (IllegalStateException e) {
304            checkCarNotConnectedExceptionFromCarService(e);
305        } catch (RemoteException e) {
306            throw new CarNotConnectedException();
307        }
308    }
309}
310