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 17 package com.android.launcher3; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.util.Log; 24 25 import java.io.*; 26 import java.util.ArrayList; 27 28 public 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