1 /*
2  * Copyright (C) 2018 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.car.settings.accounts;
18 
19 import android.accounts.Account;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.content.SyncAdapterType;
23 import android.content.SyncInfo;
24 import android.content.SyncStatusInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ProviderInfo;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.text.TextUtils;
30 
31 import com.android.car.settings.common.Logger;
32 
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Set;
36 
37 /** Helper that provides utility methods for account syncing. */
38 class AccountSyncHelper {
39     private static final Logger LOG = new Logger(AccountSyncHelper.class);
40 
AccountSyncHelper()41     private AccountSyncHelper() {
42     }
43 
44     /** Returns the visible sync adapters available for an account. */
getVisibleSyncAdaptersForAccount(Context context, Account account, UserHandle userHandle)45     static Set<SyncAdapterType> getVisibleSyncAdaptersForAccount(Context context, Account account,
46             UserHandle userHandle) {
47         Set<SyncAdapterType> syncableAdapters = getSyncableSyncAdaptersForAccount(account,
48                 userHandle);
49 
50         syncableAdapters.removeIf(
51                 (SyncAdapterType syncAdapter) -> !isVisible(context, syncAdapter, userHandle));
52 
53         return syncableAdapters;
54     }
55 
56     /** Returns the syncable sync adapters available for an account. */
getSyncableSyncAdaptersForAccount(Account account, UserHandle userHandle)57     static Set<SyncAdapterType> getSyncableSyncAdaptersForAccount(Account account,
58             UserHandle userHandle) {
59         Set<SyncAdapterType> adapters = new HashSet<>();
60 
61         SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
62                 userHandle.getIdentifier());
63         for (int i = 0; i < syncAdapters.length; i++) {
64             SyncAdapterType syncAdapter = syncAdapters[i];
65             String authority = syncAdapter.authority;
66 
67             // If the sync adapter is not for this account type, don't include it
68             if (!syncAdapter.accountType.equals(account.type)) {
69                 continue;
70             }
71 
72             boolean isSyncable = ContentResolver.getIsSyncableAsUser(account, authority,
73                     userHandle.getIdentifier()) > 0;
74             // If the adapter is not syncable, don't include it
75             if (!isSyncable) {
76                 continue;
77             }
78 
79             adapters.add(syncAdapter);
80         }
81 
82         return adapters;
83     }
84 
85     /**
86      * Requests a sync if it is allowed.
87      *
88      * <p>Derived from
89      * {@link com.android.settings.accounts.AccountSyncSettings#requestOrCancelSync}.
90      */
requestSyncIfAllowed(Account account, String authority, int userId)91     static void requestSyncIfAllowed(Account account, String authority, int userId) {
92         if (!syncIsAllowed(account, authority, userId)) {
93             return;
94         }
95 
96         Bundle extras = new Bundle();
97         extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
98         ContentResolver.requestSyncAsUser(account, authority, userId, extras);
99     }
100 
101     /**
102      * Returns the label for a given sync authority.
103      *
104      * @return the title if available, and an empty CharSequence otherwise
105      */
getTitle(Context context, String authority, UserHandle userHandle)106     static CharSequence getTitle(Context context, String authority, UserHandle userHandle) {
107         PackageManager packageManager = context.getPackageManager();
108         ProviderInfo providerInfo = packageManager.resolveContentProviderAsUser(
109                 authority, /* flags= */ 0, userHandle.getIdentifier());
110         if (providerInfo == null) {
111             return "";
112         }
113 
114         return providerInfo.loadLabel(packageManager);
115     }
116 
117     /** Returns whether a sync adapter is currently syncing for the account being shown. */
isSyncing(Account account, List<SyncInfo> currentSyncs, String authority)118     static boolean isSyncing(Account account, List<SyncInfo> currentSyncs, String authority) {
119         for (SyncInfo syncInfo : currentSyncs) {
120             if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
121                 return true;
122             }
123         }
124         return false;
125     }
126 
127     /** Returns the current sync state based on sync status information. */
getSyncState(SyncStatusInfo status, boolean syncEnabled, boolean activelySyncing)128     static SyncState getSyncState(SyncStatusInfo status, boolean syncEnabled,
129             boolean activelySyncing) {
130         boolean initialSync = status != null && status.initialize;
131         boolean syncIsPending = status != null && status.pending;
132         boolean lastSyncFailed = syncEnabled && status != null && status.lastFailureTime != 0
133                 && status.getLastFailureMesgAsInt(0)
134                 != ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
135         if (activelySyncing && !initialSync) {
136             return SyncState.ACTIVE;
137         } else if (syncIsPending && !initialSync) {
138             return SyncState.PENDING;
139         } else if (lastSyncFailed) {
140             return SyncState.FAILED;
141         }
142         return SyncState.NONE;
143     }
144 
syncIsAllowed(Account account, String authority, int userId)145     private static boolean syncIsAllowed(Account account, String authority, int userId) {
146         boolean oneTimeSyncMode = !ContentResolver.getMasterSyncAutomaticallyAsUser(userId);
147         boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
148                 userId);
149         return oneTimeSyncMode || syncEnabled;
150     }
151 
isVisible(Context context, SyncAdapterType syncAdapter, UserHandle userHandle)152     private static boolean isVisible(Context context, SyncAdapterType syncAdapter,
153             UserHandle userHandle) {
154         String authority = syncAdapter.authority;
155 
156         if (!syncAdapter.isUserVisible()) {
157             // If the sync adapter is not visible, don't show it
158             return false;
159         }
160 
161         try {
162             context.getPackageManager().getPackageUidAsUser(syncAdapter.getPackageName(),
163                     userHandle.getIdentifier());
164         } catch (PackageManager.NameNotFoundException e) {
165             LOG.e("No uid for package" + syncAdapter.getPackageName(), e);
166             // If we can't get the Uid for the package hosting the sync adapter, don't show it
167             return false;
168         }
169 
170         CharSequence title = getTitle(context, authority, userHandle);
171         if (TextUtils.isEmpty(title)) {
172             return false;
173         }
174 
175         return true;
176     }
177 
178     /** Denotes a sync adapter state. */
179     public enum SyncState {
180         /** The sync adapter is actively syncing. */
181         ACTIVE,
182         /** The sync adapter is waiting to start syncing. */
183         PENDING,
184         /** The sync adapter's last attempt to sync failed. */
185         FAILED,
186         /** Nothing to note about the sync adapter's sync state. */
187         NONE;
188     }
189 }
190