1/*
2 * Copyright (C) 2015 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 */
16package com.android.settings.applications;
17
18import android.app.AlertDialog;
19import android.app.AppOpsManager;
20import android.content.ActivityNotFoundException;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
27import android.os.Bundle;
28import android.os.UserHandle;
29import android.preference.Preference;
30import android.preference.Preference.OnPreferenceChangeListener;
31import android.preference.Preference.OnPreferenceClickListener;
32import android.preference.SwitchPreference;
33import android.provider.Settings;
34import android.util.Log;
35
36import com.android.internal.logging.MetricsLogger;
37import com.android.settings.InstrumentedFragment;
38import com.android.settings.R;
39import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
40import com.android.settings.applications.AppStateOverlayBridge.OverlayState;
41import com.android.settingslib.applications.ApplicationsState;
42import com.android.settingslib.applications.ApplicationsState.AppEntry;
43
44import java.util.List;
45
46public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
47        OnPreferenceClickListener {
48
49    private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
50    private static final String KEY_APP_OPS_SETTINGS_PREFS = "app_ops_settings_preference";
51    private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description";
52    private static final String LOG_TAG = "DrawOverlayDetails";
53
54    private static final int [] APP_OPS_OP_CODE = {
55            AppOpsManager.OP_SYSTEM_ALERT_WINDOW
56    };
57
58    // Use a bridge to get the overlay details but don't initialize it to connect with all state.
59    // TODO: Break out this functionality into its own class.
60    private AppStateOverlayBridge mOverlayBridge;
61    private AppOpsManager mAppOpsManager;
62    private SwitchPreference mSwitchPref;
63    private Preference mOverlayPrefs;
64    private Preference mOverlayDesc;
65    private Intent mSettingsIntent;
66    private OverlayState mOverlayState;
67
68    @Override
69    public void onCreate(Bundle savedInstanceState) {
70        super.onCreate(savedInstanceState);
71
72        Context context = getActivity();
73        mOverlayBridge = new AppStateOverlayBridge(context, mState, null);
74        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
75
76        // find preferences
77        addPreferencesFromResource(R.xml.app_ops_permissions_details);
78        mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
79        mOverlayPrefs = findPreference(KEY_APP_OPS_SETTINGS_PREFS);
80        mOverlayDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC);
81
82        // set title/summary for all of them
83        getPreferenceScreen().setTitle(R.string.draw_overlay);
84        mSwitchPref.setTitle(R.string.permit_draw_overlay);
85        mOverlayPrefs.setTitle(R.string.app_overlay_permission_preference);
86        mOverlayDesc.setSummary(R.string.allow_overlay_description);
87
88        // install event listeners
89        mSwitchPref.setOnPreferenceChangeListener(this);
90        mOverlayPrefs.setOnPreferenceClickListener(this);
91
92        mSettingsIntent = new Intent(Intent.ACTION_MAIN)
93                .setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
94    }
95
96    @Override
97    public boolean onPreferenceClick(Preference preference) {
98        if (preference == mOverlayPrefs) {
99            if (mSettingsIntent != null) {
100                try {
101                    getActivity().startActivityAsUser(mSettingsIntent, new UserHandle(mUserId));
102                } catch (ActivityNotFoundException e) {
103                    Log.w(LOG_TAG, "Unable to launch app draw overlay settings " + mSettingsIntent, e);
104                }
105            }
106            return true;
107        }
108        return false;
109    }
110
111    @Override
112    public boolean onPreferenceChange(Preference preference, Object newValue) {
113        if (preference == mSwitchPref) {
114            if (mOverlayState != null && (Boolean) newValue != mOverlayState.isPermissible()) {
115                setCanDrawOverlay(!mOverlayState.isPermissible());
116                refreshUi();
117            }
118            return true;
119        }
120        return false;
121    }
122
123    private void setCanDrawOverlay(boolean newState) {
124        mAppOpsManager.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
125                mPackageInfo.applicationInfo.uid, mPackageName, newState
126                ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
127    }
128
129    private boolean canDrawOverlay(String pkgName) {
130        int result = mAppOpsManager.noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
131                mPackageInfo.applicationInfo.uid, pkgName);
132        if (result == AppOpsManager.MODE_ALLOWED) {
133            return true;
134        }
135
136        return false;
137    }
138
139    @Override
140    protected boolean refreshUi() {
141        mOverlayState = mOverlayBridge.getOverlayInfo(mPackageName,
142                mPackageInfo.applicationInfo.uid);
143
144        boolean isAllowed = mOverlayState.isPermissible();
145        mSwitchPref.setChecked(isAllowed);
146        // you cannot ask a user to grant you a permission you did not have!
147        mSwitchPref.setEnabled(mOverlayState.permissionDeclared);
148        mOverlayPrefs.setEnabled(isAllowed);
149        getPreferenceScreen().removePreference(mOverlayPrefs);
150
151        return true;
152    }
153
154    @Override
155    protected AlertDialog createDialog(int id, int errorCode) {
156        return null;
157    }
158
159    @Override
160    protected int getMetricsCategory() {
161        return MetricsLogger.SYSTEM_ALERT_WINDOW_APPS;
162    }
163
164    public static CharSequence getSummary(Context context, AppEntry entry) {
165        if (entry.extraInfo != null) {
166            return getSummary(context, new OverlayState((PermissionState)entry.extraInfo));
167        }
168
169        // fallback if for whatever reason entry.extrainfo is null - the result
170        // may be less accurate
171        return getSummary(context, entry.info.packageName);
172    }
173
174    public static CharSequence getSummary(Context context, OverlayState overlayState) {
175        return context.getString(overlayState.isPermissible() ?
176            R.string.system_alert_window_on : R.string.system_alert_window_off);
177    }
178
179    public static CharSequence getSummary(Context context, String pkg) {
180        // first check if pkg is a system pkg
181        PackageManager packageManager = context.getPackageManager();
182        int uid = -1;
183        try {
184            ApplicationInfo appInfo = packageManager.getApplicationInfo(pkg, 0);
185            uid = appInfo.uid;
186            if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
187                return context.getString(R.string.system_alert_window_on);
188            }
189        } catch (PackageManager.NameNotFoundException e) {
190            // pkg doesn't even exist?
191            Log.w(LOG_TAG, "Package " + pkg + " not found", e);
192            return context.getString(R.string.system_alert_window_off);
193        }
194
195        AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context
196                .APP_OPS_SERVICE);
197        if (uid == -1) {
198            return context.getString(R.string.system_alert_window_off);
199        }
200
201        int mode = appOpsManager.noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, pkg);
202        return context.getString((mode == AppOpsManager.MODE_ALLOWED) ?
203                R.string.system_alert_window_on : R.string.system_alert_window_off);
204    }
205}
206