1/*
2**
3** Copyright 2006, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18package com.android.development;
19
20import com.android.development.R;
21
22import java.util.ArrayList;
23import java.util.HashSet;
24import java.util.List;
25
26import android.app.Activity;
27import android.app.AlertDialog;
28import android.app.Dialog;
29import android.content.Context;
30import android.content.DialogInterface;
31import android.content.Intent;
32import android.content.DialogInterface.OnCancelListener;
33import android.content.pm.ApplicationInfo;
34import android.content.pm.PackageInfo;
35import android.content.pm.PackageManager;
36import android.content.pm.PermissionInfo;
37import android.content.pm.PackageManager.NameNotFoundException;
38import android.os.Bundle;
39import android.os.Handler;
40import android.os.Message;
41import android.util.Log;
42import android.view.LayoutInflater;
43import android.view.View;
44import android.view.ViewGroup;
45import android.widget.AdapterView;
46import android.widget.BaseAdapter;
47import android.widget.ImageView;
48import android.widget.LinearLayout;
49import android.widget.ListView;
50import android.widget.TextView;
51import android.widget.AdapterView.OnItemClickListener;
52
53/**
54 * This activity displays permission details including
55 * the list of apps using a permission.
56 */
57public class PermissionDetails extends Activity implements OnCancelListener, OnItemClickListener {
58    private static final String TAG = "PermissionDetails";
59    PackageManager mPm;
60    // layout inflater object used to inflate views
61    private LayoutInflater mInflater;
62    private AppListAdapter mAdapter;
63
64    // Dialog related
65    private static final int DLG_BASE = 0;
66    private static final int DLG_ERROR = DLG_BASE + 1;
67    private static final String PROTECTION_NORMAL="Normal";
68    private static final String PROTECTION_DANGEROUS="Dangerous";
69    private static final String PROTECTION_SIGNATURE="Signature";
70    private static final String PROTECTION_SIGNATURE_OR_SYSTEM="SignatureOrSystem";
71
72    private static final String KEY_APPS_USING_PERM="AppsUsingPerm";
73
74    private static final int HANDLER_MSG_BASE = 0;
75    private static final int HANDLER_MSG_GET_APPS = HANDLER_MSG_BASE + 1;
76    private Handler mHandler = new Handler() {
77        public void handleMessage(Message msg) {
78            switch (msg.what) {
79            case HANDLER_MSG_GET_APPS:
80                ArrayList<PackageInfo> appList = msg.getData().getParcelableArrayList(KEY_APPS_USING_PERM);
81                createAppList(appList);
82                break;
83            }
84        }
85    };
86
87    // View Holder used when displaying views
88    static class AppViewHolder {
89        TextView pkgName;
90    }
91
92    class AppListAdapter extends BaseAdapter {
93        private List<PackageInfo> mList;
94
95        AppListAdapter(List<PackageInfo> list) {
96            mList = list;
97        }
98
99        public int getCount() {
100            return mList.size();
101        }
102
103        public Object getItem(int position) {
104            return mList.get(position);
105        }
106
107        public long getItemId(int position) {
108            return position;
109        }
110
111        public PackageInfo getPkg(int position) {
112            return mList.get(position);
113        }
114
115        public View getView(int position, View convertView, ViewGroup parent) {
116            // A ViewHolder keeps references to children views to avoid unneccessary calls
117            // to findViewById() on each row.
118            AppViewHolder holder;
119
120            // When convertView is not null, we can reuse it directly, there is no need
121            // to reinflate it. We only inflate a new View when the convertView supplied
122            // by ListView is null.
123            if (convertView == null) {
124                convertView = mInflater.inflate(R.layout.pkg_list_item, null);
125
126                // Creates a ViewHolder and store references to the two children views
127                // we want to bind data to.
128                holder = new AppViewHolder();
129                holder.pkgName = (TextView) convertView.findViewById(R.id.pkg_name);
130                convertView.setTag(holder);
131            } else {
132                // Get the ViewHolder back to get fast access to the TextView
133                // and the ImageView.
134                holder = (AppViewHolder) convertView.getTag();
135            }
136            // Bind the data efficiently with the holder
137            PackageInfo pInfo = mList.get(position);
138            holder.pkgName.setText(pInfo.packageName);
139            return convertView;
140        }
141    }
142
143    private void createAppList(List<PackageInfo> list) {
144        Log.i(TAG, "list.size=" + list.size());
145        for (PackageInfo pkg : list) {
146            Log.i(TAG, "Adding pkg : " +  pkg.packageName);
147        }
148        ListView listView = (ListView)findViewById(android.R.id.list);
149        mAdapter = new AppListAdapter(list);
150        ListView lv= (ListView) findViewById(android.R.id.list);
151        lv.setOnItemClickListener(this);
152        lv.setSaveEnabled(true);
153        lv.setItemsCanFocus(true);
154        listView.setAdapter(mAdapter);
155    }
156
157    private  void getAppsUsingPerm(PermissionInfo pInfo) {
158        List<PackageInfo> list = mPm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
159        HashSet<PackageInfo> set = new HashSet<PackageInfo>();
160        for (PackageInfo pkg : list) {
161            if (pkg.requestedPermissions == null) {
162                continue;
163            }
164            for (String perm : pkg.requestedPermissions) {
165                if (perm.equalsIgnoreCase(pInfo.name)) {
166                    Log.i(TAG, "Pkg:" + pkg.packageName+" uses permission");
167                    set.add(pkg);
168                    break;
169                }
170            }
171        }
172        ArrayList<PackageInfo> retList = new ArrayList<PackageInfo>();
173        for (PackageInfo pkg : set) {
174            retList.add(pkg);
175        }
176        Message msg = mHandler.obtainMessage(HANDLER_MSG_GET_APPS);
177        Bundle data = msg.getData();
178        data.putParcelableArrayList(KEY_APPS_USING_PERM, retList);
179        mHandler.dispatchMessage(msg);
180    }
181
182    @Override
183    protected void onCreate(Bundle icicle) {
184        super.onCreate(icicle);
185        setContentView(R.layout.permission_details);
186        Intent intent = getIntent();
187        String permName = intent.getStringExtra("permission");
188        if(permName == null) {
189            showDialogInner(DLG_ERROR);
190        }
191        mPm = getPackageManager();
192        mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
193        PermissionInfo pInfo = null;
194        try {
195            pInfo = mPm.getPermissionInfo(permName,
196                PackageManager.GET_PERMISSIONS);
197        } catch (NameNotFoundException e) {
198            showDialogInner(DLG_ERROR);
199        }
200        setTextView(R.id.perm_name, pInfo.name);
201        setTextView(R.id.perm_desc, pInfo.descriptionRes);
202        setTextView(R.id.perm_group, pInfo.group);
203        setProtectionLevel(R.id.perm_protection, pInfo.protectionLevel);
204        setTextView(R.id.perm_source, pInfo.packageName);
205        ApplicationInfo appInfo = null;
206        try {
207            appInfo =  mPm.getApplicationInfo(pInfo.packageName, 0);
208            String uidStr = mPm.getNameForUid(appInfo.uid);
209            setTextView(R.id.source_uid, uidStr);
210        } catch (NameNotFoundException e) {
211        }
212        boolean sharedVisibility = false;
213        // List of apps acquiring access via shared user id
214        LinearLayout sharedPanel = (LinearLayout) findViewById(R.id.shared_pkgs_panel);
215        if (appInfo != null) {
216            String[] sharedList = mPm.getPackagesForUid(appInfo.uid);
217            if ((sharedList != null) && (sharedList.length > 1)) {
218                sharedVisibility = true;
219                TextView label = (TextView) sharedPanel.findViewById(R.id.shared_pkgs_label);
220                TextView sharedView = (TextView) sharedPanel.findViewById(R.id.shared_pkgs);
221                label.setVisibility(View.VISIBLE);
222                StringBuilder buff = new StringBuilder();
223                buff.append(sharedList[0]);
224                for (int i = 1; i < sharedList.length; i++) {
225                    buff.append(", ");
226                    buff.append(sharedList[i]);
227                }
228                sharedView.setText(buff.toString());
229            }
230        }
231        if (sharedVisibility) {
232            sharedPanel.setVisibility(View.VISIBLE);
233        } else {
234            sharedPanel.setVisibility(View.GONE);
235        }
236        getAppsUsingPerm(pInfo);
237    }
238
239    private void setProtectionLevel(int viewId, int protectionLevel) {
240        String levelStr = "";
241        if (protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
242            levelStr = PROTECTION_NORMAL;
243        } else if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) {
244            levelStr = PROTECTION_DANGEROUS;
245        } else if (protectionLevel == PermissionInfo.PROTECTION_SIGNATURE) {
246            levelStr = PROTECTION_SIGNATURE;
247        } else if (protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) {
248            levelStr = PROTECTION_SIGNATURE_OR_SYSTEM;
249        } else {
250            levelStr = "Invalid";
251        }
252        setTextView(viewId, levelStr);
253    }
254
255    private void setTextView(int viewId, int textId) {
256        TextView view = (TextView)findViewById(viewId);
257        view.setText(textId);
258    }
259
260    private void setTextView(int viewId, String text) {
261        TextView view = (TextView)findViewById(viewId);
262        view.setText(text);
263    }
264
265    @Override
266    public Dialog onCreateDialog(int id) {
267        if (id == DLG_ERROR) {
268            return new AlertDialog.Builder(this)
269            .setTitle(R.string.dialog_title_error)
270            .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
271                public void onClick(DialogInterface dialog, int which) {
272                    finish();
273                }})
274            .setMessage(R.string.invalid_perm_name)
275            .setOnCancelListener(this)
276            .create();
277        }
278        return null;
279    }
280
281    private void showDialogInner(int id) {
282        // TODO better fix for this? Remove dialog so that it gets created again
283        removeDialog(id);
284        showDialog(id);
285    }
286
287    @Override
288    protected void onResume() {
289        super.onResume();
290    }
291
292    @Override
293    protected void onStop() {
294        super.onStop();
295    }
296
297    public void onCancel(DialogInterface dialog) {
298        finish();
299    }
300
301    public void onItemClick(AdapterView<?> parent, View view, int position,
302            long id) {
303        // TODO Launch app details activity
304    }
305}
306