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 }