1 /* 2 * Copyright (C) 2014 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.tests.usagestats; 18 19 import android.app.AlertDialog; 20 import android.app.ListActivity; 21 import android.app.PendingIntent; 22 import android.app.usage.UsageStats; 23 import android.app.usage.UsageStatsManager; 24 import android.content.ClipData; 25 import android.content.ClipboardManager; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.Intent; 29 import android.os.Bundle; 30 import android.text.InputType; 31 import android.text.TextUtils; 32 import android.text.format.DateUtils; 33 import android.view.LayoutInflater; 34 import android.view.Menu; 35 import android.view.MenuInflater; 36 import android.view.MenuItem; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.widget.BaseAdapter; 40 import android.widget.EditText; 41 import android.widget.TextView; 42 import android.widget.Toast; 43 44 import java.time.Duration; 45 import java.util.ArrayList; 46 import java.util.Collections; 47 import java.util.Comparator; 48 import java.util.Map; 49 import java.util.concurrent.TimeUnit; 50 51 public class UsageStatsActivity extends ListActivity { 52 private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14; 53 private static final String EXTRA_KEY_TIMEOUT = "com.android.tests.usagestats.extra.TIMEOUT"; 54 private UsageStatsManager mUsageStatsManager; 55 private ClipboardManager mClipboard; 56 private ClipData mClip; 57 private Adapter mAdapter; 58 private Comparator<UsageStats> mComparator = new Comparator<UsageStats>() { 59 @Override 60 public int compare(UsageStats o1, UsageStats o2) { 61 return Long.compare(o2.getTotalTimeInForeground(), o1.getTotalTimeInForeground()); 62 } 63 }; 64 65 @Override onCreate(Bundle savedInstanceState)66 protected void onCreate(Bundle savedInstanceState) { 67 super.onCreate(savedInstanceState); 68 mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); 69 mClipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 70 mAdapter = new Adapter(); 71 setListAdapter(mAdapter); 72 Bundle extras = getIntent().getExtras(); 73 if (extras != null && extras.containsKey(UsageStatsManager.EXTRA_TIME_USED)) { 74 System.err.println("UsageStatsActivity " + extras); 75 Toast.makeText(this, "Timeout of observed app\n" + extras, Toast.LENGTH_SHORT).show(); 76 } 77 } 78 79 @Override onNewIntent(Intent intent)80 public void onNewIntent(Intent intent) { 81 Bundle extras = intent.getExtras(); 82 if (extras != null && extras.containsKey(UsageStatsManager.EXTRA_TIME_USED)) { 83 System.err.println("UsageStatsActivity " + extras); 84 Toast.makeText(this, "Timeout of observed app\n" + extras, Toast.LENGTH_SHORT).show(); 85 } 86 } 87 88 @Override onCreateOptionsMenu(Menu menu)89 public boolean onCreateOptionsMenu(Menu menu) { 90 MenuInflater inflater = getMenuInflater(); 91 inflater.inflate(R.menu.main, menu); 92 return super.onCreateOptionsMenu(menu); 93 } 94 95 @Override onOptionsItemSelected(MenuItem item)96 public boolean onOptionsItemSelected(MenuItem item) { 97 switch (item.getItemId()) { 98 case R.id.log: 99 startActivity(new Intent(this, UsageLogActivity.class)); 100 return true; 101 case R.id.call_is_app_inactive: 102 callIsAppInactive(); 103 return true; 104 case R.id.set_app_limit: 105 callSetAppLimit(); 106 return true; 107 case R.id.set_app_usage_limit: 108 callSetAppUsageLimit(); 109 default: 110 return super.onOptionsItemSelected(item); 111 } 112 } 113 114 @Override onResume()115 protected void onResume() { 116 super.onResume(); 117 updateAdapter(); 118 } 119 callIsAppInactive()120 private void callIsAppInactive() { 121 final AlertDialog.Builder builder = new AlertDialog.Builder(this); 122 builder.setTitle("Enter package name"); 123 final EditText input = new EditText(this); 124 input.setInputType(InputType.TYPE_CLASS_TEXT); 125 input.setHint("com.android.tests.usagestats"); 126 builder.setView(input); 127 128 builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { 129 @Override 130 public void onClick(DialogInterface dialog, int which) { 131 final String packageName = input.getText().toString().trim(); 132 if (!TextUtils.isEmpty(packageName)) { 133 showInactive(packageName); 134 } 135 } 136 }); 137 builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { 138 @Override 139 public void onClick(DialogInterface dialog, int which) { 140 dialog.cancel(); 141 } 142 }); 143 144 builder.show(); 145 } 146 callSetAppLimit()147 private void callSetAppLimit() { 148 final AlertDialog.Builder builder = new AlertDialog.Builder(this); 149 builder.setTitle("Enter package name"); 150 final EditText input = new EditText(this); 151 input.setInputType(InputType.TYPE_CLASS_TEXT); 152 input.setHint("com.android.tests.usagestats"); 153 builder.setView(input); 154 155 builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { 156 @Override 157 public void onClick(DialogInterface dialog, int which) { 158 final String packageName = input.getText().toString().trim(); 159 if (!TextUtils.isEmpty(packageName)) { 160 String[] packages = packageName.split(","); 161 Intent intent = new Intent(Intent.ACTION_MAIN); 162 intent.setClass(UsageStatsActivity.this, UsageStatsActivity.class); 163 intent.setPackage(getPackageName()); 164 intent.putExtra(EXTRA_KEY_TIMEOUT, true); 165 mUsageStatsManager.registerAppUsageObserver(1, packages, 166 60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this, 167 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED)); 168 } 169 } 170 }); 171 builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { 172 @Override 173 public void onClick(DialogInterface dialog, int which) { 174 dialog.cancel(); 175 } 176 }); 177 178 builder.show(); 179 } 180 callSetAppUsageLimit()181 private void callSetAppUsageLimit() { 182 final AlertDialog.Builder builder = new AlertDialog.Builder(this); 183 builder.setTitle("Enter package name"); 184 final EditText input = new EditText(this); 185 input.setInputType(InputType.TYPE_CLASS_TEXT); 186 input.setHint("com.android.tests.usagestats"); 187 builder.setView(input); 188 189 builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { 190 @Override 191 public void onClick(DialogInterface dialog, int which) { 192 final String packageName = input.getText().toString().trim(); 193 if (!TextUtils.isEmpty(packageName)) { 194 String[] packages = packageName.split(","); 195 Intent intent = new Intent(Intent.ACTION_MAIN); 196 intent.setClass(UsageStatsActivity.this, UsageStatsActivity.class); 197 intent.setPackage(getPackageName()); 198 intent.putExtra(EXTRA_KEY_TIMEOUT, true); 199 mUsageStatsManager.registerAppUsageLimitObserver(1, packages, 200 Duration.ofSeconds(60), Duration.ofSeconds(0), 201 PendingIntent.getActivity(UsageStatsActivity.this, 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED)); 202 } 203 } 204 }); 205 builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { 206 @Override 207 public void onClick(DialogInterface dialog, int which) { 208 dialog.cancel(); 209 } 210 }); 211 212 builder.show(); 213 } 214 showInactive(String packageName)215 private void showInactive(String packageName) { 216 final AlertDialog.Builder builder = new AlertDialog.Builder(this); 217 builder.setMessage( 218 "isAppInactive(\"" + packageName + "\") = " 219 + (mUsageStatsManager.isAppInactive(packageName) ? "true" : "false")); 220 builder.show(); 221 } 222 updateAdapter()223 private void updateAdapter() { 224 long now = System.currentTimeMillis(); 225 long beginTime = now - USAGE_STATS_PERIOD; 226 Map<String, UsageStats> stats = mUsageStatsManager.queryAndAggregateUsageStats( 227 beginTime, now); 228 mAdapter.update(stats); 229 } 230 231 private class Adapter extends BaseAdapter { 232 private ArrayList<UsageStats> mStats = new ArrayList<>(); 233 update(Map<String, UsageStats> stats)234 public void update(Map<String, UsageStats> stats) { 235 mStats.clear(); 236 if (stats == null) { 237 return; 238 } 239 240 mStats.addAll(stats.values()); 241 Collections.sort(mStats, mComparator); 242 notifyDataSetChanged(); 243 } 244 245 @Override getCount()246 public int getCount() { 247 return mStats.size(); 248 } 249 250 @Override getItem(int position)251 public Object getItem(int position) { 252 return mStats.get(position); 253 } 254 255 @Override getItemId(int position)256 public long getItemId(int position) { 257 return position; 258 } 259 260 @Override getView(int position, View convertView, ViewGroup parent)261 public View getView(int position, View convertView, ViewGroup parent) { 262 final ViewHolder holder; 263 if (convertView == null) { 264 convertView = LayoutInflater.from(UsageStatsActivity.this) 265 .inflate(R.layout.row_item, parent, false); 266 holder = new ViewHolder(); 267 holder.packageName = (TextView) convertView.findViewById(android.R.id.text1); 268 holder.usageTime = (TextView) convertView.findViewById(android.R.id.text2); 269 convertView.setTag(holder); 270 } else { 271 holder = (ViewHolder) convertView.getTag(); 272 } 273 274 holder.packageName.setText(mStats.get(position).getPackageName()); 275 holder.usageTime.setText(DateUtils.formatDuration( 276 mStats.get(position).getTotalTimeInForeground())); 277 278 //copy package name to the clipboard for convenience 279 holder.packageName.setOnLongClickListener(new View.OnLongClickListener() { 280 @Override 281 public boolean onLongClick(View v) { 282 String text = holder.packageName.getText().toString(); 283 mClip = ClipData.newPlainText("package_name", text); 284 mClipboard.setPrimaryClip(mClip); 285 286 Toast.makeText(getApplicationContext(), "package name copied to clipboard", 287 Toast.LENGTH_SHORT).show(); 288 return true; 289 } 290 }); 291 292 return convertView; 293 } 294 } 295 296 private static class ViewHolder { 297 TextView packageName; 298 TextView usageTime; 299 } 300 }