1/*
2 * Copyright (C) 2012 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.launcher3;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.util.Log;
24
25import java.io.*;
26import java.util.ArrayList;
27
28public class Stats {
29    private static final boolean DEBUG_BROADCASTS = false;
30    private static final String TAG = "Launcher3/Stats";
31
32    private static final boolean LOCAL_LAUNCH_LOG = true;
33
34    public static final String ACTION_LAUNCH = "com.android.launcher3.action.LAUNCH";
35    public static final String EXTRA_INTENT = "intent";
36    public static final String EXTRA_CONTAINER = "container";
37    public static final String EXTRA_SCREEN = "screen";
38    public static final String EXTRA_CELLX = "cellX";
39    public static final String EXTRA_CELLY = "cellY";
40
41    private static final String LOG_FILE_NAME = "launches.log";
42    private static final int LOG_VERSION = 1;
43    private static final int LOG_TAG_VERSION = 0x1;
44    private static final int LOG_TAG_LAUNCH = 0x1000;
45
46    private static final String STATS_FILE_NAME = "stats.log";
47    private static final int STATS_VERSION = 1;
48    private static final int INITIAL_STATS_SIZE = 100;
49
50    // TODO: delayed/batched writes
51    private static final boolean FLUSH_IMMEDIATELY = true;
52
53    private final Launcher mLauncher;
54
55    private final String mLaunchBroadcastPermission;
56
57    DataOutputStream mLog;
58
59    ArrayList<String> mIntents;
60    ArrayList<Integer> mHistogram;
61
62    public Stats(Launcher launcher) {
63        mLauncher = launcher;
64
65        mLaunchBroadcastPermission =
66                launcher.getResources().getString(R.string.receive_launch_broadcasts_permission);
67
68        loadStats();
69
70        if (LOCAL_LAUNCH_LOG) {
71            try {
72                mLog = new DataOutputStream(mLauncher.openFileOutput(LOG_FILE_NAME, Context.MODE_APPEND));
73                mLog.writeInt(LOG_TAG_VERSION);
74                mLog.writeInt(LOG_VERSION);
75            } catch (FileNotFoundException e) {
76                Log.e(TAG, "unable to create stats log: " + e);
77                mLog = null;
78            } catch (IOException e) {
79                Log.e(TAG, "unable to write to stats log: " + e);
80                mLog = null;
81            }
82        }
83
84        if (DEBUG_BROADCASTS) {
85            launcher.registerReceiver(
86                    new BroadcastReceiver() {
87                        @Override
88                        public void onReceive(Context context, Intent intent) {
89                            android.util.Log.v("Stats", "got broadcast: " + intent + " for launched intent: "
90                                    + intent.getStringExtra(EXTRA_INTENT));
91                        }
92                    },
93                    new IntentFilter(ACTION_LAUNCH),
94                    mLaunchBroadcastPermission,
95                    null
96            );
97        }
98    }
99
100    public void incrementLaunch(String intentStr) {
101        int pos = mIntents.indexOf(intentStr);
102        if (pos < 0) {
103            mIntents.add(intentStr);
104            mHistogram.add(1);
105        } else {
106            mHistogram.set(pos, mHistogram.get(pos) + 1);
107        }
108    }
109
110    public void recordLaunch(Intent intent) {
111        recordLaunch(intent, null);
112    }
113
114    public void recordLaunch(Intent intent, ShortcutInfo shortcut) {
115        intent = new Intent(intent);
116        intent.setSourceBounds(null);
117
118        final String flat = intent.toUri(0);
119
120        Intent broadcastIntent = new Intent(ACTION_LAUNCH).putExtra(EXTRA_INTENT, flat);
121        if (shortcut != null) {
122            broadcastIntent.putExtra(EXTRA_CONTAINER, shortcut.container)
123                    .putExtra(EXTRA_SCREEN, shortcut.screenId)
124                    .putExtra(EXTRA_CELLX, shortcut.cellX)
125                    .putExtra(EXTRA_CELLY, shortcut.cellY);
126        }
127        mLauncher.sendBroadcast(broadcastIntent, mLaunchBroadcastPermission);
128
129        incrementLaunch(flat);
130
131        if (FLUSH_IMMEDIATELY) {
132            saveStats();
133        }
134
135        if (LOCAL_LAUNCH_LOG && mLog != null) {
136            try {
137                mLog.writeInt(LOG_TAG_LAUNCH);
138                mLog.writeLong(System.currentTimeMillis());
139                if (shortcut == null) {
140                    mLog.writeShort(0);
141                    mLog.writeShort(0);
142                    mLog.writeShort(0);
143                    mLog.writeShort(0);
144                } else {
145                    mLog.writeShort((short) shortcut.container);
146                    mLog.writeShort((short) shortcut.screenId);
147                    mLog.writeShort((short) shortcut.cellX);
148                    mLog.writeShort((short) shortcut.cellY);
149                }
150                mLog.writeUTF(flat);
151                if (FLUSH_IMMEDIATELY) {
152                    mLog.flush(); // TODO: delayed writes
153                }
154            } catch (IOException e) {
155                e.printStackTrace();
156            }
157        }
158    }
159
160    private void saveStats() {
161        DataOutputStream stats = null;
162        try {
163            stats = new DataOutputStream(mLauncher.openFileOutput(STATS_FILE_NAME + ".tmp", Context.MODE_PRIVATE));
164            stats.writeInt(STATS_VERSION);
165            final int N = mHistogram.size();
166            stats.writeInt(N);
167            for (int i=0; i<N; i++) {
168                stats.writeUTF(mIntents.get(i));
169                stats.writeInt(mHistogram.get(i));
170            }
171            stats.close();
172            stats = null;
173            mLauncher.getFileStreamPath(STATS_FILE_NAME + ".tmp")
174                     .renameTo(mLauncher.getFileStreamPath(STATS_FILE_NAME));
175        } catch (FileNotFoundException e) {
176            Log.e(TAG, "unable to create stats data: " + e);
177        } catch (IOException e) {
178            Log.e(TAG, "unable to write to stats data: " + e);
179        } finally {
180            if (stats != null) {
181                try {
182                    stats.close();
183                } catch (IOException e) { }
184            }
185        }
186    }
187
188    private void loadStats() {
189        mIntents = new ArrayList<String>(INITIAL_STATS_SIZE);
190        mHistogram = new ArrayList<Integer>(INITIAL_STATS_SIZE);
191        DataInputStream stats = null;
192        try {
193            stats = new DataInputStream(mLauncher.openFileInput(STATS_FILE_NAME));
194            final int version = stats.readInt();
195            if (version == STATS_VERSION) {
196                final int N = stats.readInt();
197                for (int i=0; i<N; i++) {
198                    final String pkg = stats.readUTF();
199                    final int count = stats.readInt();
200                    mIntents.add(pkg);
201                    mHistogram.add(count);
202                }
203            }
204        } catch (FileNotFoundException e) {
205            // not a problem
206        } catch (IOException e) {
207            // more of a problem
208
209        } finally {
210            if (stats != null) {
211                try {
212                    stats.close();
213                } catch (IOException e) { }
214            }
215        }
216    }
217}
218