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  */
16 
17 package com.android.settings.applications;
18 
19 import android.app.AlertDialog;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.IntentFilterVerificationInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.support.v7.preference.DropDownPreference;
30 import android.support.v7.preference.Preference;
31 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
32 import android.util.ArraySet;
33 import android.util.Log;
34 import android.view.View;
35 import android.view.View.OnClickListener;
36 
37 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
38 import com.android.settings.R;
39 import com.android.settings.Utils;
40 
41 import java.util.List;
42 
43 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
44 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
45 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
46 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
47 
48 public class AppLaunchSettings extends AppInfoWithHeader implements OnClickListener,
49         Preference.OnPreferenceChangeListener {
50     private static final String TAG = "AppLaunchSettings";
51 
52     private static final String KEY_APP_LINK_STATE = "app_link_state";
53     private static final String KEY_SUPPORTED_DOMAIN_URLS = "app_launch_supported_domain_urls";
54     private static final String KEY_CLEAR_DEFAULTS = "app_launch_clear_defaults";
55 
56     private static final Intent sBrowserIntent;
57     static {
58         sBrowserIntent = new Intent()
59                 .setAction(Intent.ACTION_VIEW)
60                 .addCategory(Intent.CATEGORY_BROWSABLE)
61                 .setData(Uri.parse("http:"));
62     }
63 
64     private PackageManager mPm;
65 
66     private boolean mIsBrowser;
67     private boolean mHasDomainUrls;
68     private DropDownPreference mAppLinkState;
69     private AppDomainsPreference mAppDomainUrls;
70     private ClearDefaultsPreference mClearDefaultsPreference;
71 
72     @Override
onCreate(Bundle savedInstanceState)73     public void onCreate(Bundle savedInstanceState) {
74         super.onCreate(savedInstanceState);
75 
76         addPreferencesFromResource(R.xml.installed_app_launch_settings);
77         mAppDomainUrls = (AppDomainsPreference) findPreference(KEY_SUPPORTED_DOMAIN_URLS);
78         mClearDefaultsPreference = (ClearDefaultsPreference) findPreference(KEY_CLEAR_DEFAULTS);
79         mAppLinkState = (DropDownPreference) findPreference(KEY_APP_LINK_STATE);
80 
81         mPm = getActivity().getPackageManager();
82 
83         mIsBrowser = isBrowserApp(mPackageName);
84         mHasDomainUrls =
85                 (mAppEntry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
86 
87         if (!mIsBrowser) {
88             List<IntentFilterVerificationInfo> iviList = mPm.getIntentFilterVerifications(mPackageName);
89             List<IntentFilter> filters = mPm.getAllIntentFilters(mPackageName);
90             CharSequence[] entries = getEntries(mPackageName, iviList, filters);
91             mAppDomainUrls.setTitles(entries);
92             mAppDomainUrls.setValues(new int[entries.length]);
93         }
94         buildStateDropDown();
95     }
96 
97     // An app is a "browser" if it has an activity resolution that wound up
98     // marked with the 'handleAllWebDataURI' flag.
isBrowserApp(String packageName)99     private boolean isBrowserApp(String packageName) {
100         sBrowserIntent.setPackage(packageName);
101         List<ResolveInfo> list = mPm.queryIntentActivitiesAsUser(sBrowserIntent,
102                 PackageManager.MATCH_ALL, UserHandle.myUserId());
103         final int count = list.size();
104         for (int i = 0; i < count; i++) {
105             ResolveInfo info = list.get(i);
106             if (info.activityInfo != null && info.handleAllWebDataURI) {
107                 return true;
108             }
109         }
110         return false;
111     }
112 
buildStateDropDown()113     private void buildStateDropDown() {
114         if (mIsBrowser) {
115             // Browsers don't show the app-link prefs
116             mAppLinkState.setShouldDisableView(true);
117             mAppLinkState.setEnabled(false);
118             mAppDomainUrls.setShouldDisableView(true);
119             mAppDomainUrls.setEnabled(false);
120         } else {
121             // Designed order of states in the dropdown:
122             //
123             // * always
124             // * ask
125             // * never
126             mAppLinkState.setEntries(new CharSequence[] {
127                     getString(R.string.app_link_open_always),
128                     getString(R.string.app_link_open_ask),
129                     getString(R.string.app_link_open_never),
130             });
131             mAppLinkState.setEntryValues(new CharSequence[] {
132                     Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS),
133                     Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK),
134                     Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER),
135             });
136 
137             mAppLinkState.setEnabled(mHasDomainUrls);
138             if (mHasDomainUrls) {
139                 // Present 'undefined' as 'ask' because the OS treats them identically for
140                 // purposes of the UI (and does the right thing around pending domain
141                 // verifications that might arrive after the user chooses 'ask' in this UI).
142                 final int state = mPm.getIntentVerificationStatusAsUser(mPackageName, UserHandle.myUserId());
143                 mAppLinkState.setValue(
144                         Integer.toString((state == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED)
145                                 ? INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK
146                                         : state));
147 
148                 // Set the callback only after setting the initial selected item
149                 mAppLinkState.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
150                     @Override
151                     public boolean onPreferenceChange(Preference preference, Object newValue) {
152                         return updateAppLinkState(Integer.parseInt((String) newValue));
153                     }
154                 });
155             }
156         }
157     }
158 
updateAppLinkState(final int newState)159     private boolean updateAppLinkState(final int newState) {
160         if (mIsBrowser) {
161             // We shouldn't get into this state, but if we do make sure
162             // not to cause any permanent mayhem.
163             return false;
164         }
165 
166         final int userId = UserHandle.myUserId();
167         final int priorState = mPm.getIntentVerificationStatusAsUser(mPackageName, userId);
168 
169         if (priorState == newState) {
170             return false;
171         }
172 
173         boolean success = mPm.updateIntentVerificationStatusAsUser(mPackageName, newState, userId);
174         if (success) {
175             // Read back the state to see if the change worked
176             final int updatedState = mPm.getIntentVerificationStatusAsUser(mPackageName, userId);
177             success = (newState == updatedState);
178         } else {
179             Log.e(TAG, "Couldn't update intent verification status!");
180         }
181         return success;
182     }
183 
getEntries(String packageName, List<IntentFilterVerificationInfo> iviList, List<IntentFilter> filters)184     private CharSequence[] getEntries(String packageName, List<IntentFilterVerificationInfo> iviList,
185             List<IntentFilter> filters) {
186         ArraySet<String> result = Utils.getHandledDomains(mPm, packageName);
187         return result.toArray(new CharSequence[result.size()]);
188     }
189 
190     @Override
refreshUi()191     protected boolean refreshUi() {
192         mClearDefaultsPreference.setPackageName(mPackageName);
193         mClearDefaultsPreference.setAppEntry(mAppEntry);
194         return true;
195     }
196 
197     @Override
createDialog(int id, int errorCode)198     protected AlertDialog createDialog(int id, int errorCode) {
199         // No dialogs for preferred launch settings.
200         return null;
201     }
202 
203     @Override
onClick(View v)204     public void onClick(View v) {
205         // Nothing to do
206     }
207 
208     @Override
onPreferenceChange(Preference preference, Object newValue)209     public boolean onPreferenceChange(Preference preference, Object newValue) {
210         // actual updates are handled by the app link dropdown callback
211         return true;
212     }
213 
214     @Override
getMetricsCategory()215     public int getMetricsCategory() {
216         return MetricsEvent.APPLICATIONS_APP_LAUNCH;
217     }
218 }
219