1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.settings.applications;
16
17import android.app.Application;
18import android.content.ComponentName;
19import android.content.Context;
20import android.content.Intent;
21import android.content.pm.PackageManager;
22import android.os.Bundle;
23import android.os.UserHandle;
24import android.provider.Settings;
25import android.provider.Settings.Global;
26import android.support.annotation.VisibleForTesting;
27import android.support.v14.preference.SwitchPreference;
28import android.support.v7.preference.Preference;
29import android.support.v7.preference.Preference.OnPreferenceChangeListener;
30import android.support.v7.preference.Preference.OnPreferenceClickListener;
31import android.support.v7.preference.PreferenceCategory;
32import android.support.v7.preference.PreferenceGroup;
33import android.support.v7.preference.PreferenceViewHolder;
34import android.util.ArraySet;
35import android.view.View;
36
37import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
38import com.android.settings.R;
39import com.android.settings.SettingsPreferenceFragment;
40import com.android.settings.Utils;
41import com.android.settings.widget.AppPreference;
42import com.android.settingslib.applications.ApplicationsState;
43import com.android.settingslib.applications.ApplicationsState.AppEntry;
44
45import java.util.ArrayList;
46
47/**
48 * Activity to manage how Android handles URL resolution. Includes both per-app
49 * handling as well as system handling for Web Actions.
50 */
51public class ManageDomainUrls extends SettingsPreferenceFragment
52        implements ApplicationsState.Callbacks, OnPreferenceChangeListener,
53        OnPreferenceClickListener {
54
55    // constant value that can be used to check return code from sub activity.
56    private static final int INSTALLED_APP_DETAILS = 1;
57
58    private ApplicationsState mApplicationsState;
59    private ApplicationsState.Session mSession;
60    private PreferenceGroup mDomainAppList;
61    private SwitchPreference mWebAction;
62    private Preference mInstantAppAccountPreference;
63
64    @Override
65    public void onCreate(Bundle icicle) {
66        super.onCreate(icicle);
67        setAnimationAllowed(true);
68        mApplicationsState = ApplicationsState.getInstance(
69                (Application) getContext().getApplicationContext());
70        mSession = mApplicationsState.newSession(this, getLifecycle());
71        setHasOptionsMenu(true);
72    }
73
74    @Override
75    protected int getPreferenceScreenResId() {
76        return R.xml.manage_domain_url_settings;
77    }
78
79    @Override
80    public void onViewCreated(View view, Bundle savedInstanceState) {
81        super.onViewCreated(view, savedInstanceState);
82    }
83
84    @Override
85    public void onRunningStateChanged(boolean running) {
86    }
87
88    @Override
89    public void onPackageListChanged() {
90    }
91
92    @Override
93    public void onRebuildComplete(ArrayList<AppEntry> apps) {
94        if (getContext() == null) {
95            return;
96        }
97
98        final boolean disableWebActions = Global.getInt(getContext().getContentResolver(),
99                Global.ENABLE_EPHEMERAL_FEATURE, 1) == 0;
100        if (disableWebActions) {
101            mDomainAppList = getPreferenceScreen();
102        } else {
103            final PreferenceGroup preferenceScreen = getPreferenceScreen();
104            if (preferenceScreen.getPreferenceCount() == 0) {
105                // add preferences
106                final PreferenceCategory webActionCategory =
107                        new PreferenceCategory(getPrefContext());
108                webActionCategory.setTitle(R.string.web_action_section_title);
109                preferenceScreen.addPreference(webActionCategory);
110
111                // toggle to enable / disable Web Actions [aka Instant Apps]
112                mWebAction = new SwitchPreference(getPrefContext());
113                mWebAction.setTitle(R.string.web_action_enable_title);
114                mWebAction.setSummary(R.string.web_action_enable_summary);
115                mWebAction.setChecked(Settings.Secure.getInt(getContentResolver(),
116                        Settings.Secure.INSTANT_APPS_ENABLED, 1) != 0);
117                mWebAction.setOnPreferenceChangeListener(this);
118                webActionCategory.addPreference(mWebAction);
119
120                // Determine whether we should show the instant apps account chooser setting
121                ComponentName instantAppSettingsComponent = getActivity().getPackageManager()
122                        .getInstantAppResolverSettingsComponent();
123                Intent instantAppSettingsIntent = null;
124                if (instantAppSettingsComponent != null) {
125                    instantAppSettingsIntent =
126                            new Intent().setComponent(instantAppSettingsComponent);
127                }
128                if (instantAppSettingsIntent != null) {
129                    final Intent launchIntent = instantAppSettingsIntent;
130                    // TODO: Make this button actually launch the account chooser.
131                    mInstantAppAccountPreference = new Preference(getPrefContext());
132                    mInstantAppAccountPreference.setTitle(R.string.instant_apps_settings);
133                    mInstantAppAccountPreference.setOnPreferenceClickListener(pref -> {
134                        startActivity(launchIntent);
135                        return true;
136                    });
137                    webActionCategory.addPreference(mInstantAppAccountPreference);
138                }
139
140                // list to manage link handling per app
141                mDomainAppList = new PreferenceCategory(getPrefContext());
142                mDomainAppList.setTitle(R.string.domain_url_section_title);
143                preferenceScreen.addPreference(mDomainAppList);
144            }
145        }
146        rebuildAppList(mDomainAppList, apps);
147    }
148
149    @Override
150    public boolean onPreferenceChange(Preference preference, Object newValue) {
151        if (preference == mWebAction) {
152            boolean checked = (boolean) newValue;
153            Settings.Secure.putInt(
154                    getContentResolver(),
155                    Settings.Secure.INSTANT_APPS_ENABLED, checked ? 1 : 0);
156            return true;
157        }
158        return false;
159    }
160
161    private void rebuild() {
162        final ArrayList<AppEntry> apps = mSession.rebuild(
163                ApplicationsState.FILTER_WITH_DOMAIN_URLS, ApplicationsState.ALPHA_COMPARATOR);
164        if (apps != null) {
165            onRebuildComplete(apps);
166        }
167    }
168
169    private void rebuildAppList(PreferenceGroup group, ArrayList<AppEntry> apps) {
170        cacheRemoveAllPrefs(group);
171        final int N = apps.size();
172        for (int i = 0; i < N; i++) {
173            AppEntry entry = apps.get(i);
174            String key = entry.info.packageName + "|" + entry.info.uid;
175            DomainAppPreference preference = (DomainAppPreference) getCachedPreference(key);
176            if (preference == null) {
177                preference = new DomainAppPreference(getPrefContext(), mApplicationsState, entry);
178                preference.setKey(key);
179                preference.setOnPreferenceClickListener(this);
180                group.addPreference(preference);
181            } else {
182                preference.reuse();
183            }
184            preference.setOrder(i);
185        }
186        removeCachedPrefs(group);
187    }
188
189    @Override
190    public void onPackageIconChanged() {
191    }
192
193    @Override
194    public void onPackageSizeChanged(String packageName) {
195    }
196
197    @Override
198    public void onAllSizesComputed() {
199    }
200
201    @Override
202    public void onLauncherInfoChanged() {
203    }
204
205    @Override
206    public void onLoadEntriesCompleted() {
207        rebuild();
208    }
209
210    @Override
211    public int getMetricsCategory() {
212        return MetricsEvent.MANAGE_DOMAIN_URLS;
213    }
214
215    @Override
216    public boolean onPreferenceClick(Preference preference) {
217        if (preference.getClass() == DomainAppPreference.class) {
218            ApplicationsState.AppEntry entry = ((DomainAppPreference) preference).mEntry;
219            AppInfoBase.startAppInfoFragment(AppLaunchSettings.class, R.string.auto_launch_label,
220                    entry.info.packageName, entry.info.uid, this,
221                    INSTALLED_APP_DETAILS, getMetricsCategory());
222            return true;
223        }
224        return false;
225    }
226
227    @VisibleForTesting
228    static class DomainAppPreference extends AppPreference {
229        private final AppEntry mEntry;
230        private final PackageManager mPm;
231        private final ApplicationsState mApplicationsState;
232
233        public DomainAppPreference(final Context context, ApplicationsState applicationsState,
234                AppEntry entry) {
235            super(context);
236            mApplicationsState = applicationsState;
237            mPm = context.getPackageManager();
238            mEntry = entry;
239            mEntry.ensureLabel(getContext());
240            setState();
241            if (mEntry.icon != null) {
242                setIcon(mEntry.icon);
243            }
244        }
245
246        private void setState() {
247            setTitle(mEntry.label);
248            setSummary(getDomainsSummary(mEntry.info.packageName));
249        }
250
251        public void reuse() {
252            setState();
253            notifyChanged();
254        }
255
256        @Override
257        public void onBindViewHolder(PreferenceViewHolder holder) {
258            if (mEntry.icon == null) {
259                holder.itemView.post(new Runnable() {
260                    @Override
261                    public void run() {
262                        // Ensure we have an icon before binding.
263                        mApplicationsState.ensureIcon(mEntry);
264                        // This might trigger us to bind again, but it gives an easy way to only
265                        // load the icon once its needed, so its probably worth it.
266                        setIcon(mEntry.icon);
267                    }
268                });
269            }
270            super.onBindViewHolder(holder);
271        }
272
273        private CharSequence getDomainsSummary(String packageName) {
274            // If the user has explicitly said "no" for this package, that's the
275            // string we should show.
276            int domainStatus =
277                    mPm.getIntentVerificationStatusAsUser(packageName, UserHandle.myUserId());
278            if (domainStatus == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
279                return getContext().getString(R.string.domain_urls_summary_none);
280            }
281            // Otherwise, ask package manager for the domains for this package,
282            // and show the first one (or none if there aren't any).
283            ArraySet<String> result = Utils.getHandledDomains(mPm, packageName);
284            if (result.size() == 0) {
285                return getContext().getString(R.string.domain_urls_summary_none);
286            } else if (result.size() == 1) {
287                return getContext().getString(R.string.domain_urls_summary_one, result.valueAt(0));
288            } else {
289                return getContext().getString(R.string.domain_urls_summary_some, result.valueAt(0));
290            }
291        }
292    }
293}
294