blob: 5650f21257370f6ef2a102423f812c30140a85ee [file] [log] [blame]
Chalard Jean8c141bd2018-12-04 20:20:56 +09001/*
2 * Copyright (C) 2018 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
Chalard Jean95213512019-01-30 21:04:58 +090017package com.android.server.connectivity.ipmemorystore;
Chalard Jean8c141bd2018-12-04 20:20:56 +090018
Chalard Jean91549b62018-12-18 22:05:19 +090019import static android.net.ipmemorystore.Status.ERROR_DATABASE_CANNOT_BE_OPENED;
20import static android.net.ipmemorystore.Status.ERROR_GENERIC;
21import static android.net.ipmemorystore.Status.ERROR_ILLEGAL_ARGUMENT;
Chalard Jeanbf73e662018-12-27 20:59:41 +090022import static android.net.ipmemorystore.Status.SUCCESS;
Chalard Jean91549b62018-12-18 22:05:19 +090023
Chalard Jean95213512019-01-30 21:04:58 +090024import static com.android.server.connectivity.ipmemorystore.IpMemoryStoreDatabase.EXPIRY_ERROR;
paulhu028d7a52019-03-26 01:39:10 +080025import static com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService.InterruptMaintenance;
Chalard Jean91549b62018-12-18 22:05:19 +090026
Chalard Jean8c141bd2018-12-04 20:20:56 +090027import android.annotation.NonNull;
Chalard Jeanf89d7be2018-12-07 23:09:02 +090028import android.annotation.Nullable;
Chalard Jean8c141bd2018-12-04 20:20:56 +090029import android.content.Context;
Chalard Jean61e27ab2018-12-12 17:56:37 +090030import android.database.SQLException;
31import android.database.sqlite.SQLiteDatabase;
Chalard Jean8c141bd2018-12-04 20:20:56 +090032import android.net.IIpMemoryStore;
Chalard Jeanf89d7be2018-12-07 23:09:02 +090033import android.net.ipmemorystore.Blob;
34import android.net.ipmemorystore.IOnBlobRetrievedListener;
35import android.net.ipmemorystore.IOnL2KeyResponseListener;
36import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
37import android.net.ipmemorystore.IOnSameNetworkResponseListener;
38import android.net.ipmemorystore.IOnStatusListener;
Chalard Jean91549b62018-12-18 22:05:19 +090039import android.net.ipmemorystore.NetworkAttributes;
Chalard Jeanf89d7be2018-12-07 23:09:02 +090040import android.net.ipmemorystore.NetworkAttributesParcelable;
Chalard Jeana39756a2019-01-16 18:18:44 +090041import android.net.ipmemorystore.SameL3NetworkResponse;
Chalard Jean91549b62018-12-18 22:05:19 +090042import android.net.ipmemorystore.Status;
43import android.net.ipmemorystore.StatusParcelable;
Chalard Jean91549b62018-12-18 22:05:19 +090044import android.os.RemoteException;
Chalard Jean61e27ab2018-12-12 17:56:37 +090045import android.util.Log;
Chalard Jean8c141bd2018-12-04 20:20:56 +090046
paulhu028d7a52019-03-26 01:39:10 +080047import com.android.internal.annotations.VisibleForTesting;
48
49import java.io.File;
Chalard Jean0150bd92018-12-13 23:32:01 +090050import java.util.concurrent.ExecutorService;
51import java.util.concurrent.Executors;
52
Chalard Jean8c141bd2018-12-04 20:20:56 +090053/**
54 * Implementation for the IP memory store.
55 * This component offers specialized services for network components to store and retrieve
56 * knowledge about networks, and provides intelligence that groups level 2 networks together
57 * into level 3 networks.
Chalard Jeanf89d7be2018-12-07 23:09:02 +090058 *
Chalard Jean8c141bd2018-12-04 20:20:56 +090059 * @hide
60 */
61public class IpMemoryStoreService extends IIpMemoryStore.Stub {
Chalard Jean61e27ab2018-12-12 17:56:37 +090062 private static final String TAG = IpMemoryStoreService.class.getSimpleName();
Chalard Jean0150bd92018-12-13 23:32:01 +090063 private static final int MAX_CONCURRENT_THREADS = 4;
paulhu028d7a52019-03-26 01:39:10 +080064 private static final int DATABASE_SIZE_THRESHOLD = 10 * 1024 * 1024; //10MB
65 private static final int MAX_DROP_RECORD_TIMES = 500;
66 private static final int MIN_DELETE_NUM = 5;
Chalard Jean91549b62018-12-18 22:05:19 +090067 private static final boolean DBG = true;
Chalard Jean8c141bd2018-12-04 20:20:56 +090068
paulhu028d7a52019-03-26 01:39:10 +080069 // Error codes below are internal and used for notifying status beteween IpMemoryStore modules.
70 static final int ERROR_INTERNAL_BASE = -1_000_000_000;
71 // This error code is used for maintenance only to notify RegularMaintenanceJobService that
72 // full maintenance job has been interrupted.
73 static final int ERROR_INTERNAL_INTERRUPTED = ERROR_INTERNAL_BASE - 1;
74
Chalard Jean61e27ab2018-12-12 17:56:37 +090075 @NonNull
76 final Context mContext;
77 @Nullable
78 final SQLiteDatabase mDb;
Chalard Jean0150bd92018-12-13 23:32:01 +090079 @NonNull
80 final ExecutorService mExecutor;
Chalard Jean61e27ab2018-12-12 17:56:37 +090081
82 /**
83 * Construct an IpMemoryStoreService object.
84 * This constructor will block on disk access to open the database.
85 * @param context the context to access storage with.
86 */
Chalard Jean8c141bd2018-12-04 20:20:56 +090087 public IpMemoryStoreService(@NonNull final Context context) {
Chalard Jean61e27ab2018-12-12 17:56:37 +090088 // Note that constructing the service will access the disk and block
89 // for some time, but it should make no difference to the clients. Because
90 // the interface is one-way, clients fire and forget requests, and the callback
91 // will get called eventually in any case, and the framework will wait for the
92 // service to be created to deliver subsequent requests.
93 // Avoiding this would mean the mDb member can't be final, which means the service would
94 // have to test for nullity, care for failure, and allow for a wait at every single access,
95 // which would make the code a lot more complex and require all methods to possibly block.
Chalard Jean8c141bd2018-12-04 20:20:56 +090096 mContext = context;
Chalard Jean61e27ab2018-12-12 17:56:37 +090097 SQLiteDatabase db;
98 final IpMemoryStoreDatabase.DbHelper helper = new IpMemoryStoreDatabase.DbHelper(context);
99 try {
100 db = helper.getWritableDatabase();
101 if (null == db) Log.e(TAG, "Unexpected null return of getWriteableDatabase");
102 } catch (final SQLException e) {
103 Log.e(TAG, "Can't open the Ip Memory Store database", e);
104 db = null;
105 } catch (final Exception e) {
106 Log.wtf(TAG, "Impossible exception Ip Memory Store database", e);
107 db = null;
108 }
109 mDb = db;
Chalard Jean0150bd92018-12-13 23:32:01 +0900110 // The work-stealing thread pool executor will spawn threads as needed up to
111 // the max only when there is no free thread available. This generally behaves
112 // exactly like one would expect it intuitively :
113 // - When work arrives, it will spawn a new thread iff there are no available threads
114 // - When there is no work to do it will shutdown threads after a while (the while
115 // being equal to 2 seconds (not configurable) when max threads are spun up and
116 // twice as much for every one less thread)
117 // - When all threads are busy the work is enqueued and waits for any worker
118 // to become available.
119 // Because the stealing pool is made for very heavily parallel execution of
120 // small tasks that spawn others, it creates a queue per thread that in this
121 // case is overhead. However, the three behaviors above make it a superior
122 // choice to cached or fixedThreadPoolExecutor, neither of which can actually
123 // enqueue a task waiting for a thread to be free. This can probably be solved
124 // with judicious subclassing of ThreadPoolExecutor, but that's a lot of dangerous
125 // complexity for little benefit in this case.
126 mExecutor = Executors.newWorkStealingPool(MAX_CONCURRENT_THREADS);
paulhu028d7a52019-03-26 01:39:10 +0800127 RegularMaintenanceJobService.schedule(mContext, this);
Chalard Jean0150bd92018-12-13 23:32:01 +0900128 }
129
130 /**
131 * Shutdown the memory store service, cancelling running tasks and dropping queued tasks.
132 *
133 * This is provided to give a way to clean up, and is meant to be available in case of an
134 * emergency shutdown.
135 */
136 public void shutdown() {
137 // By contrast with ExecutorService#shutdown, ExecutorService#shutdownNow tries
138 // to cancel the existing tasks, and does not wait for completion. It does not
139 // guarantee the threads can be terminated in any given amount of time.
140 mExecutor.shutdownNow();
141 if (mDb != null) mDb.close();
paulhu028d7a52019-03-26 01:39:10 +0800142 RegularMaintenanceJobService.unschedule(mContext);
Chalard Jean8c141bd2018-12-04 20:20:56 +0900143 }
144
Chalard Jean91549b62018-12-18 22:05:19 +0900145 /** Helper function to make a status object */
146 private StatusParcelable makeStatus(final int code) {
147 return new Status(code).toParcelable();
148 }
149
Chalard Jean8c141bd2018-12-04 20:20:56 +0900150 /**
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900151 * Store network attributes for a given L2 key.
152 *
153 * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
154 * key and only care about grouping can pass a unique ID here like the ones
155 * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
156 * relevance of such a network will lead to it being evicted soon if it's not
157 * refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
158 * @param attributes The attributes for this network.
159 * @param listener A listener to inform of the completion of this call, or null if the client
160 * is not interested in learning about success/failure.
161 * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
162 * If the call failed, the L2 key will be null.
Chalard Jean8c141bd2018-12-04 20:20:56 +0900163 */
Chalard Jean91549b62018-12-18 22:05:19 +0900164 // Note that while l2Key and attributes are non-null in spirit, they are received from
165 // another process. If the remote process decides to ignore everything and send null, this
166 // process should still not crash.
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900167 @Override
Chalard Jean91549b62018-12-18 22:05:19 +0900168 public void storeNetworkAttributes(@Nullable final String l2Key,
169 @Nullable final NetworkAttributesParcelable attributes,
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900170 @Nullable final IOnStatusListener listener) {
Chalard Jean91549b62018-12-18 22:05:19 +0900171 // Because the parcelable is 100% mutable, the thread may not see its members initialized.
172 // Therefore either an immutable object is created on this same thread before it's passed
173 // to the executor, or there need to be a write barrier here and a read barrier in the
174 // remote thread.
175 final NetworkAttributes na = null == attributes ? null : new NetworkAttributes(attributes);
176 mExecutor.execute(() -> {
177 try {
178 final int code = storeNetworkAttributesAndBlobSync(l2Key, na,
179 null /* clientId */, null /* name */, null /* data */);
180 if (null != listener) listener.onComplete(makeStatus(code));
181 } catch (final RemoteException e) {
182 // Client at the other end died
183 }
184 });
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900185 }
186
187 /**
188 * Store a binary blob associated with an L2 key and a name.
189 *
190 * @param l2Key The L2 key for this network.
191 * @param clientId The ID of the client.
192 * @param name The name of this data.
Chalard Jean91549b62018-12-18 22:05:19 +0900193 * @param blob The data to store.
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900194 * @param listener The listener that will be invoked to return the answer, or null if the
195 * is not interested in learning about success/failure.
196 * Through the listener, returns a status to indicate success or failure.
197 */
198 @Override
Chalard Jean91549b62018-12-18 22:05:19 +0900199 public void storeBlob(@Nullable final String l2Key, @Nullable final String clientId,
200 @Nullable final String name, @Nullable final Blob blob,
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900201 @Nullable final IOnStatusListener listener) {
Chalard Jean91549b62018-12-18 22:05:19 +0900202 final byte[] data = null == blob ? null : blob.data;
203 mExecutor.execute(() -> {
204 try {
205 final int code = storeNetworkAttributesAndBlobSync(l2Key,
206 null /* NetworkAttributes */, clientId, name, data);
207 if (null != listener) listener.onComplete(makeStatus(code));
208 } catch (final RemoteException e) {
209 // Client at the other end died
210 }
211 });
212 }
213
214 /**
215 * Helper method for storeNetworkAttributes and storeBlob.
216 *
217 * Either attributes or none of clientId, name and data may be null. This will write the
218 * passed data if non-null, and will write attributes if non-null, but in any case it will
219 * bump the relevance up.
220 * Returns a success code from Status.
221 */
222 private int storeNetworkAttributesAndBlobSync(@Nullable final String l2Key,
223 @Nullable final NetworkAttributes attributes,
224 @Nullable final String clientId,
225 @Nullable final String name, @Nullable final byte[] data) {
226 if (null == l2Key) return ERROR_ILLEGAL_ARGUMENT;
227 if (null == attributes && null == data) return ERROR_ILLEGAL_ARGUMENT;
228 if (null != data && (null == clientId || null == name)) return ERROR_ILLEGAL_ARGUMENT;
229 if (null == mDb) return ERROR_DATABASE_CANNOT_BE_OPENED;
230 try {
231 final long oldExpiry = IpMemoryStoreDatabase.getExpiry(mDb, l2Key);
232 final long newExpiry = RelevanceUtils.bumpExpiryDate(
233 oldExpiry == EXPIRY_ERROR ? System.currentTimeMillis() : oldExpiry);
234 final int errorCode =
235 IpMemoryStoreDatabase.storeNetworkAttributes(mDb, l2Key, newExpiry, attributes);
236 // If no blob to store, the client is interested in the result of storing the attributes
237 if (null == data) return errorCode;
238 // Otherwise it's interested in the result of storing the blob
239 return IpMemoryStoreDatabase.storeBlob(mDb, l2Key, clientId, name, data);
240 } catch (Exception e) {
241 if (DBG) {
242 Log.e(TAG, "Exception while storing for key {" + l2Key
243 + "} ; NetworkAttributes {" + (null == attributes ? "null" : attributes)
244 + "} ; clientId {" + (null == clientId ? "null" : clientId)
245 + "} ; name {" + (null == name ? "null" : name)
246 + "} ; data {" + Utils.byteArrayToString(data) + "}", e);
247 }
248 }
249 return ERROR_GENERIC;
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900250 }
251
252 /**
253 * Returns the best L2 key associated with the attributes.
254 *
255 * This will find a record that would be in the same group as the passed attributes. This is
256 * useful to choose the key for storing a sample or private data when the L2 key is not known.
257 * If multiple records are group-close to these attributes, the closest match is returned.
258 * If multiple records have the same closeness, the one with the smaller (unicode codepoint
259 * order) L2 key is returned.
260 * If no record matches these attributes, null is returned.
261 *
262 * @param attributes The attributes of the network to find.
263 * @param listener The listener that will be invoked to return the answer.
264 * Through the listener, returns the L2 key if one matched, or null.
265 */
266 @Override
Chalard Jean8d1a8902019-01-18 20:21:26 +0900267 public void findL2Key(@Nullable final NetworkAttributesParcelable attributes,
268 @Nullable final IOnL2KeyResponseListener listener) {
269 if (null == listener) return;
270 mExecutor.execute(() -> {
271 try {
272 if (null == attributes) {
273 listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
274 return;
275 }
276 if (null == mDb) {
277 listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
278 return;
279 }
280 final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb,
281 new NetworkAttributes(attributes));
282 listener.onL2KeyResponse(makeStatus(SUCCESS), key);
283 } catch (final RemoteException e) {
284 // Client at the other end died
285 }
286 });
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900287 }
288
289 /**
290 * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
291 * to the same L3 network. Group-closeness is used to determine this.
292 *
293 * @param l2Key1 The key for the first network.
294 * @param l2Key2 The key for the second network.
295 * @param listener The listener that will be invoked to return the answer.
296 * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
297 */
298 @Override
Chalard Jeana39756a2019-01-16 18:18:44 +0900299 public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2,
300 @Nullable final IOnSameNetworkResponseListener listener) {
301 if (null == listener) return;
302 mExecutor.execute(() -> {
303 try {
304 if (null == l2Key1 || null == l2Key2) {
305 listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
306 return;
307 }
308 if (null == mDb) {
309 listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
310 return;
311 }
312 try {
313 final NetworkAttributes attr1 =
314 IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key1);
315 final NetworkAttributes attr2 =
316 IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2);
317 if (null == attr1 || null == attr2) {
318 listener.onSameNetworkResponse(makeStatus(SUCCESS),
319 new SameL3NetworkResponse(l2Key1, l2Key2,
320 -1f /* never connected */).toParcelable());
321 return;
322 }
323 final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2);
324 listener.onSameNetworkResponse(makeStatus(SUCCESS),
325 new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable());
326 } catch (Exception e) {
327 listener.onSameNetworkResponse(makeStatus(ERROR_GENERIC), null);
328 }
329 } catch (final RemoteException e) {
330 // Client at the other end died
331 }
332 });
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900333 }
334
335 /**
336 * Retrieve the network attributes for a key.
337 * If no record is present for this key, this will return null attributes.
338 *
339 * @param l2Key The key of the network to query.
340 * @param listener The listener that will be invoked to return the answer.
341 * Through the listener, returns the network attributes and the L2 key associated with
342 * the query.
343 */
344 @Override
Chalard Jeanbf73e662018-12-27 20:59:41 +0900345 public void retrieveNetworkAttributes(@Nullable final String l2Key,
346 @Nullable final IOnNetworkAttributesRetrieved listener) {
347 if (null == listener) return;
348 mExecutor.execute(() -> {
349 try {
350 if (null == l2Key) {
Chalard Jeanb67e4932019-01-16 23:05:10 +0900351 listener.onNetworkAttributesRetrieved(
352 makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
Chalard Jeanbf73e662018-12-27 20:59:41 +0900353 return;
354 }
355 if (null == mDb) {
Chalard Jeanb67e4932019-01-16 23:05:10 +0900356 listener.onNetworkAttributesRetrieved(
357 makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, null);
Chalard Jeanbf73e662018-12-27 20:59:41 +0900358 return;
359 }
360 try {
361 final NetworkAttributes attributes =
362 IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key);
Chalard Jeanb67e4932019-01-16 23:05:10 +0900363 listener.onNetworkAttributesRetrieved(makeStatus(SUCCESS), l2Key,
Chalard Jeanbf73e662018-12-27 20:59:41 +0900364 null == attributes ? null : attributes.toParcelable());
365 } catch (final Exception e) {
Chalard Jeanb67e4932019-01-16 23:05:10 +0900366 listener.onNetworkAttributesRetrieved(makeStatus(ERROR_GENERIC), l2Key, null);
Chalard Jeanbf73e662018-12-27 20:59:41 +0900367 }
368 } catch (final RemoteException e) {
369 // Client at the other end died
370 }
371 });
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900372 }
373
374 /**
375 * Retrieve previously stored private data.
376 * If no data was stored for this L2 key and name this will return null.
377 *
378 * @param l2Key The L2 key.
379 * @param clientId The id of the client that stored this data.
380 * @param name The name of the data.
381 * @param listener The listener that will be invoked to return the answer.
382 * Through the listener, returns the private data if any or null if none, with the L2 key
383 * and the name of the data associated with the query.
384 */
385 @Override
386 public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
387 @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
Chalard Jeanbf73e662018-12-27 20:59:41 +0900388 if (null == listener) return;
389 mExecutor.execute(() -> {
390 try {
391 if (null == l2Key) {
392 listener.onBlobRetrieved(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, name, null);
393 return;
394 }
395 if (null == mDb) {
396 listener.onBlobRetrieved(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
397 name, null);
398 return;
399 }
400 try {
401 final Blob b = new Blob();
402 b.data = IpMemoryStoreDatabase.retrieveBlob(mDb, l2Key, clientId, name);
403 listener.onBlobRetrieved(makeStatus(SUCCESS), l2Key, name, b);
404 } catch (final Exception e) {
405 listener.onBlobRetrieved(makeStatus(ERROR_GENERIC), l2Key, name, null);
406 }
407 } catch (final RemoteException e) {
408 // Client at the other end died
409 }
410 });
Chalard Jean8c141bd2018-12-04 20:20:56 +0900411 }
paulhu028d7a52019-03-26 01:39:10 +0800412
413 /** Get db size threshold. */
414 @VisibleForTesting
415 protected int getDbSizeThreshold() {
416 return DATABASE_SIZE_THRESHOLD;
417 }
418
419 private long getDbSize() {
420 final File dbFile = new File(mDb.getPath());
421 try {
422 return dbFile.length();
423 } catch (final SecurityException e) {
424 if (DBG) Log.e(TAG, "Read db size access deny.", e);
425 // Return zero value if can't get disk usage exactly.
426 return 0;
427 }
428 }
429
430 /** Check if db size is over the threshold. */
431 @VisibleForTesting
432 boolean isDbSizeOverThreshold() {
433 return getDbSize() > getDbSizeThreshold();
434 }
435
436 /**
437 * Full maintenance.
438 *
439 * @param listener A listener to inform of the completion of this call.
440 */
441 void fullMaintenance(@NonNull final IOnStatusListener listener,
442 @NonNull final InterruptMaintenance interrupt) {
443 mExecutor.execute(() -> {
444 try {
445 if (null == mDb) {
446 listener.onComplete(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED));
447 return;
448 }
449
450 // Interrupt maintenance because the scheduling job has been canceled.
451 if (checkForInterrupt(listener, interrupt)) return;
452
453 int result = SUCCESS;
454 // Drop all records whose relevance has decayed to zero.
455 // This is the first step to decrease memory store size.
456 result = IpMemoryStoreDatabase.dropAllExpiredRecords(mDb);
457
458 if (checkForInterrupt(listener, interrupt)) return;
459
460 // Aggregate historical data in passes
461 // TODO : Waiting for historical data implement.
462
463 // Check if db size meets the storage goal(10MB). If not, keep dropping records and
464 // aggregate historical data until the storage goal is met. Use for loop with 500
465 // times restriction to prevent infinite loop (Deleting records always fail and db
466 // size is still over the threshold)
467 for (int i = 0; isDbSizeOverThreshold() && i < MAX_DROP_RECORD_TIMES; i++) {
468 if (checkForInterrupt(listener, interrupt)) return;
469
470 final int totalNumber = IpMemoryStoreDatabase.getTotalRecordNumber(mDb);
471 final long dbSize = getDbSize();
472 final float decreaseRate = (dbSize == 0)
473 ? 0 : (float) (dbSize - getDbSizeThreshold()) / (float) dbSize;
474 final int deleteNumber = Math.max(
475 (int) (totalNumber * decreaseRate), MIN_DELETE_NUM);
476
477 result = IpMemoryStoreDatabase.dropNumberOfRecords(mDb, deleteNumber);
478
479 if (checkForInterrupt(listener, interrupt)) return;
480
481 // Aggregate historical data
482 // TODO : Waiting for historical data implement.
483 }
484 listener.onComplete(makeStatus(result));
485 } catch (final RemoteException e) {
486 // Client at the other end died
487 }
488 });
489 }
490
491 private boolean checkForInterrupt(@NonNull final IOnStatusListener listener,
492 @NonNull final InterruptMaintenance interrupt) throws RemoteException {
493 if (!interrupt.isInterrupted()) return false;
494 listener.onComplete(makeStatus(ERROR_INTERNAL_INTERRUPTED));
495 return true;
496 }
Chalard Jean8c141bd2018-12-04 20:20:56 +0900497}