1/*
2 * Copyright (C) 2016 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
17package com.android.server.pm;
18
19import android.os.Environment;
20import android.os.SystemClock;
21import android.util.AtomicFile;
22
23import java.io.File;
24import java.util.concurrent.atomic.AtomicBoolean;
25import java.util.concurrent.atomic.AtomicLong;
26
27/**
28 * A simple base class for statistics that need to be saved/restored from a dedicated file. This
29 * class provides a base implementation that:
30 * <ul>
31 * <li>Provide an AtomicFile to the actual read/write code
32 * <li>A background-thread write and a synchronous write
33 * <li>Write-limiting for the background-thread (by default writes are at least 30 minutes apart)
34 * <li>Can lock on the provided data object before writing
35 * </ul>
36 * For completion, a subclass needs to implement actual {@link #writeInternal(Object) writing} and
37 * {@link #readInternal(Object) reading}.
38 */
39public abstract class AbstractStatsBase<T> {
40
41    private static final int WRITE_INTERVAL_MS =
42            (PackageManagerService.DEBUG_DEXOPT) ? 0 : 30*60*1000;
43    private final Object mFileLock = new Object();
44    private final AtomicLong mLastTimeWritten = new AtomicLong(0);
45    private final AtomicBoolean mBackgroundWriteRunning = new AtomicBoolean(false);
46    private final String mFileName;
47    private final String mBackgroundThreadName;
48    private final boolean mLock;
49
50    protected AbstractStatsBase(String fileName, String threadName, boolean lock) {
51        mFileName = fileName;
52        mBackgroundThreadName = threadName;
53        mLock = lock;
54    }
55
56    protected AtomicFile getFile() {
57        File dataDir = Environment.getDataDirectory();
58        File systemDir = new File(dataDir, "system");
59        File fname = new File(systemDir, mFileName);
60        return new AtomicFile(fname);
61    }
62
63    protected void writeNow(final T data) {
64        writeImpl(data);
65        mLastTimeWritten.set(SystemClock.elapsedRealtime());
66    }
67
68    protected boolean maybeWriteAsync(final T data) {
69        if (SystemClock.elapsedRealtime() - mLastTimeWritten.get() < WRITE_INTERVAL_MS
70            && !PackageManagerService.DEBUG_DEXOPT) {
71            return false;
72        }
73
74        if (mBackgroundWriteRunning.compareAndSet(false, true)) {
75            new Thread(mBackgroundThreadName) {
76                @Override
77                public void run() {
78                    try {
79                        writeImpl(data);
80                        mLastTimeWritten.set(SystemClock.elapsedRealtime());
81                    } finally {
82                        mBackgroundWriteRunning.set(false);
83                    }
84                }
85            }.start();
86            return true;
87        }
88
89        return false;
90    }
91
92    private void writeImpl(T data) {
93        if (mLock) {
94            synchronized (data) {
95                synchronized (mFileLock) {
96                    writeInternal(data);
97                }
98            }
99        } else {
100            synchronized (mFileLock) {
101                writeInternal(data);
102            }
103        }
104    }
105
106    protected abstract void writeInternal(T data);
107
108    protected void read(T data) {
109        if (mLock) {
110            synchronized (data) {
111                synchronized (mFileLock) {
112                    readInternal(data);
113                }
114            }
115        } else {
116            synchronized (mFileLock) {
117                readInternal(data);
118            }
119        }
120        // We use the current time as last-written. read() is called on system server startup
121        // (current situation), and we want to postpone I/O at boot.
122        mLastTimeWritten.set(SystemClock.elapsedRealtime());
123    }
124
125    protected abstract void readInternal(T data);
126}
127