aboutsummaryrefslogtreecommitdiff
path: root/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
blob: 2320952c2c2529e7d3e26e4551a0d51261c42bc3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.car.storagemonitoring;

import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.car.Car;
import android.car.CarManagerBase;
import android.car.CarNotConnectedException;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import com.android.car.internal.SingleMessageHandler;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static android.car.CarApiUtil.checkCarNotConnectedExceptionFromCarService;

/**
 * API for retrieving information and metrics about the flash storage.
 *
 * @hide
 */
@SystemApi
public final class CarStorageMonitoringManager implements CarManagerBase {
    private static final String TAG = CarStorageMonitoringManager.class.getSimpleName();
    private static final int MSG_IO_STATS_EVENT = 0;

    private final ICarStorageMonitoring mService;
    private ListenerToService mListenerToService;
    private final SingleMessageHandler<IoStats> mMessageHandler;
    private final Set<IoStatsListener> mListeners = new HashSet<>();

    public interface IoStatsListener {
        void onSnapshot(IoStats snapshot);
    }
    private static final class ListenerToService extends IIoStatsListener.Stub {
        private final WeakReference<CarStorageMonitoringManager> mManager;

        ListenerToService(CarStorageMonitoringManager manager) {
            mManager = new WeakReference<>(manager);
        }

        @Override
        public void onSnapshot(IoStats snapshot) {
            CarStorageMonitoringManager manager = mManager.get();
            if (manager != null) {
                manager.mMessageHandler.sendEvents(Collections.singletonList(snapshot));
            }
        }
    }

    public static final String INTENT_EXCESSIVE_IO = "android.car.storagemonitoring.EXCESSIVE_IO";

    public static final int PRE_EOL_INFO_UNKNOWN = 0;
    public static final int PRE_EOL_INFO_NORMAL = 1;
    public static final int PRE_EOL_INFO_WARNING = 2;
    public static final int PRE_EOL_INFO_URGENT = 3;

    public static final long SHUTDOWN_COST_INFO_MISSING = -1;

    /**
     * @hide
     */
    public CarStorageMonitoringManager(IBinder service, Handler handler) {
        mService = ICarStorageMonitoring.Stub.asInterface(service);
        mMessageHandler = new SingleMessageHandler<IoStats>(handler, MSG_IO_STATS_EVENT) {
            @Override
            protected void handleEvent(IoStats event) {
                for (IoStatsListener listener : mListeners) {
                    listener.onSnapshot(event);
                }
            }
        };
    }

    /**
     * @hide
     */
    @Override
    public void onCarDisconnected() {
        mListeners.clear();
        mListenerToService = null;
    }

    // ICarStorageMonitoring forwards

    /**
     * This method returns the value of the "pre EOL" indicator for the flash storage
     * as retrieved during the current boot cycle.
     *
     * It will return either PRE_EOL_INFO_UNKNOWN if the value can't be determined,
     * or one of PRE_EOL_INFO_{NORMAL|WARNING|URGENT} depending on the device state.
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public int getPreEolIndicatorStatus() throws CarNotConnectedException {
        try {
            return mService.getPreEolIndicatorStatus();
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
        return PRE_EOL_INFO_UNKNOWN;
    }

    /**
     * This method returns the value of the wear estimate indicators for the flash storage
     * as retrieved during the current boot cycle.
     *
     * The indicators are guaranteed to be a lower-bound on the actual wear of the storage.
     * Current technology in common automotive usage offers estimates in 10% increments.
     *
     * If either or both indicators are not available, they will be reported as UNKNOWN.
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public WearEstimate getWearEstimate() throws CarNotConnectedException {
        try {
            return mService.getWearEstimate();
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
        return WearEstimate.UNKNOWN_ESTIMATE;
    }

    /**
     * This method returns a list of all changes in wear estimate indicators detected during the
     * lifetime of the system.
     *
     * The indicators are not guaranteed to persist across a factory reset.
     *
     * The indicators are guaranteed to be a lower-bound on the actual wear of the storage.
     * Current technology in common automotive usage offers estimates in 10% increments.
     *
     * If no indicators are available, an empty list will be returned.
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public List<WearEstimateChange> getWearEstimateHistory() throws CarNotConnectedException {
        try {
            return mService.getWearEstimateHistory();
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
        return Collections.emptyList();
    }

    /**
     * This method returns a list of per user-id I/O activity metrics as collected at the end of
     * system boot.
     *
     * The BOOT_COMPLETE broadcast is used as the trigger to collect this data. The implementation
     * may impose an additional, and even variable across boot cycles, delay between the sending
     * of the broadcast and the collection of the data.
     *
     * If the information is not available, an empty list will be returned.
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public List<IoStatsEntry> getBootIoStats() throws CarNotConnectedException {
        try {
            return mService.getBootIoStats();
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
        return Collections.emptyList();
    }

    /**
     * This method returns an approximation of the number of bytes written to disk during
     * the course of the previous system shutdown.
     *
     * <p>For purposes of this API the system shutdown is defined as starting when CarService
     * receives the ACTION_SHUTDOWN or ACTION_REBOOT intent from the system.</p>
     *
     * <p>The information provided by this API does not provide attribution of the disk writes to
     * specific applications or system daemons.</p>
     *
     * <p>The information returned by this call is a best effort guess, whose accuracy depends
     * on the underlying file systems' ability to reliably track and accumulate
     * disk write sizes.</p>
     *
     * <p>A corrupt file system, or one which was not cleanly unmounted during shutdown, may
     * be unable to provide any information, or may provide incorrect data. While the API
     * will attempt to detect these scenarios, the detection may fail and incorrect data
     * may end up being used in calculations.</p>
     *
     * <p>If the information is not available, SHUTDOWN_COST_INFO_MISSING will be returned.</p>s
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public long getShutdownDiskWriteAmount() throws CarNotConnectedException {
        try {
            return mService.getShutdownDiskWriteAmount();
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
        return SHUTDOWN_COST_INFO_MISSING;
    }

    /**
     * This method returns a list of per user-id I/O activity metrics as collected from kernel
     * start until the last snapshot.
     *
     * The samples provided might be as old as the value of the ioStatsRefreshRateSeconds setting.
     *
     * If the information is not available, an empty list will be returned.
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public List<IoStatsEntry> getAggregateIoStats() throws CarNotConnectedException {
        try {
            return mService.getAggregateIoStats();
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
        return Collections.emptyList();
    }

    /**
     * This method returns a list of the I/O stats deltas currently stored by the system.
     *
     * Periodically, the system gathers I/O activity metrics and computes and stores a delta from
     * the previous cycle. The timing and the number of these stored samples are configurable
     * by the OEM.
     *
     * The samples are returned in order from the oldest to the newest.
     *
     * If the information is not available, an empty list will be returned.
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public List<IoStats> getIoStatsDeltas() throws CarNotConnectedException {
        try {
            return mService.getIoStatsDeltas();
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
        return Collections.emptyList();
    }

    /**
     * This method registers a new listener to receive I/O stats deltas.
     *
     * The system periodically gathers I/O activity metrics and computes a delta of such
     * activity. Registered listeners will receive those deltas as they are available.
     *
     * The timing of availability of the deltas is configurable by the OEM.
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public void registerListener(IoStatsListener listener) throws CarNotConnectedException {
        try {
            if (mListeners.isEmpty()) {
                if (mListenerToService == null) {
                    mListenerToService = new ListenerToService(this);
                }
                mService.registerListener(mListenerToService);
            }
            mListeners.add(listener);
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
    }

    /**
     * This method removes a registered listener of I/O stats deltas.
     */
    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
    public void unregisterListener(IoStatsListener listener) throws CarNotConnectedException {
        try {
            if (!mListeners.remove(listener)) {
                return;
            }
            if (mListeners.isEmpty()) {
                mService.unregisterListener(mListenerToService);
                mListenerToService = null;
            }
        } catch (IllegalStateException e) {
            checkCarNotConnectedExceptionFromCarService(e);
        } catch (RemoteException e) {
            throw new CarNotConnectedException();
        }
    }
}