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 int LOG_VERSION = 1;
42    private static final int LOG_TAG_VERSION = 0x1;
43    private static final int LOG_TAG_LAUNCH = 0x1000;
44
45    private static final int STATS_VERSION = 1;
46    private static final int INITIAL_STATS_SIZE = 100;
47
48    // TODO: delayed/batched writes
49    private static final boolean FLUSH_IMMEDIATELY = true;
50
51    private final Launcher mLauncher;
52
53    private final String mLaunchBroadcastPermission;
54
55    DataOutputStream mLog;
56
57    ArrayList<String> mIntents;
58    ArrayList<Integer> mHistogram;
59
60    public Stats(Launcher launcher) {
61        mLauncher = launcher;
62
63        mLaunchBroadcastPermission =
64                launcher.getResources().getString(R.string.receive_launch_broadcasts_permission);
65
66        loadStats();
67
68        if (LOCAL_LAUNCH_LOG) {
69            try {
70                mLog = new DataOutputStream(mLauncher.openFileOutput(
71                        LauncherFiles.LAUNCHES_LOG, Context.MODE_APPEND));
72                mLog.writeInt(LOG_TAG_VERSION);
73                mLog.writeInt(LOG_VERSION);
74            } catch (FileNotFoundException e) {
75                Log.e(TAG, "unable to create stats log: " + e);
76                mLog = null;
77            } catch (IOException e) {
78                Log.e(TAG, "unable to write to stats log: " + e);
79                mLog = null;
80            }
81        }
82
83        if (DEBUG_BROADCASTS) {
84            launcher.registerReceiver(
85                    new BroadcastReceiver() {
86                        @Override
87                        public void onReceive(Context context, Intent intent) {
88                            android.util.Log.v("Stats", "got broadcast: " + intent + " for launched intent: "
89                                    + intent.getStringExtra(EXTRA_INTENT));
90                        }
91                    },
92                    new IntentFilter(ACTION_LAUNCH),
93                    mLaunchBroadcastPermission,
94                    null
95            );
96        }
97    }
98
99    public void incrementLaunch(String intentStr) {
100        int pos = mIntents.indexOf(intentStr);
101        if (pos < 0) {
102            mIntents.add(intentStr);
103            mHistogram.add(1);
104        } else {
105            mHistogram.set(pos, mHistogram.get(pos) + 1);
106        }
107    }
108
109    public void recordLaunch(Intent intent) {
110        recordLaunch(intent, null);
111    }
112
113    public void recordLaunch(Intent intent, ShortcutInfo shortcut) {
114        intent = new Intent(intent);
115        intent.setSourceBounds(null);
116
117        final String flat = intent.toUri(0);
118
119        Intent broadcastIntent = new Intent(ACTION_LAUNCH).putExtra(EXTRA_INTENT, flat);
120        if (shortcut != null) {
121            broadcastIntent.putExtra(EXTRA_CONTAINER, shortcut.container)
122                    .putExtra(EXTRA_SCREEN, shortcut.screenId)
123                    .putExtra(EXTRA_CELLX, shortcut.cellX)
124                    .putExtra(EXTRA_CELLY, shortcut.cellY);
125        }
126        mLauncher.sendBroadcast(broadcastIntent, mLaunchBroadcastPermission);
127
128        incrementLaunch(flat);
129
130        if (FLUSH_IMMEDIATELY) {
131            saveStats();
132        }
133
134        if (LOCAL_LAUNCH_LOG && mLog != null) {
135            try {
136                mLog.writeInt(LOG_TAG_LAUNCH);
137                mLog.writeLong(System.currentTimeMillis());
138                if (shortcut == null) {
139                    mLog.writeShort(0);
140                    mLog.writeShort(0);
141                    mLog.writeShort(0);
142                    mLog.writeShort(0);
143                } else {
144                    mLog.writeShort((short) shortcut.container);
145                    mLog.writeShort((short) shortcut.screenId);
146                    mLog.writeShort((short) shortcut.cellX);
147                    mLog.writeShort((short) shortcut.cellY);
148                }
149                mLog.writeUTF(flat);
150                if (FLUSH_IMMEDIATELY) {
151                    mLog.flush(); // TODO: delayed writes
152                }
153            } catch (IOException e) {
154                e.printStackTrace();
155            }
156        }
157    }
158
159    private void saveStats() {
160        DataOutputStream stats = null;
161        try {
162            stats = new DataOutputStream(mLauncher.openFileOutput(
163                    LauncherFiles.STATS_LOG + ".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(LauncherFiles.STATS_LOG + ".tmp")
174                     .renameTo(mLauncher.getFileStreamPath(LauncherFiles.STATS_LOG));
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(LauncherFiles.STATS_LOG));
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