• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.google.android.car.kitchensink.backup;
18 
19 import android.annotation.Nullable;
20 import android.app.AlertDialog;
21 import android.app.backup.BackupManager;
22 import android.app.backup.RestoreObserver;
23 import android.app.backup.RestoreSession;
24 import android.app.backup.RestoreSet;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.util.Log;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.Button;
34 
35 import androidx.fragment.app.Fragment;
36 
37 import com.google.android.car.kitchensink.R;
38 
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.List;
42 import java.util.concurrent.CountDownLatch;
43 import java.util.concurrent.Executors;
44 import java.util.concurrent.TimeUnit;
45 
46 public final class BackupAndRestoreFragment extends Fragment {
47 
48     private static final String TAG = BackupAndRestoreFragment.class.getSimpleName();
49     private static final String TRANSPORT_DIR_NAME =
50             "com.google.android.car.kitchensink.backup.KitchenSinkBackupTransport";
51     private static final long CURRENT_SET_TOKEN = 1;
52 
53     private final int mUserId = UserHandle.myUserId();
54 
55     private BackupManager mBackupManager;
56 
57     private Button mBackupButton;
58     private Button mRestoreButton;
59     private Button mShowTransportButton;
60 
61     @Override
onCreate(Bundle savedInstanceState)62     public void onCreate(Bundle savedInstanceState) {
63         super.onCreate(savedInstanceState);
64         mBackupManager = new BackupManager(getContext());
65     }
66 
67     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)68     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
69             @Nullable Bundle savedInstanceState) {
70         return inflater.inflate(R.layout.backup_restore_fragment, container, false);
71     }
72 
73     @Override
onViewCreated(View view, Bundle savedInstanceState)74     public void onViewCreated(View view, Bundle savedInstanceState) {
75         mShowTransportButton = view.findViewById(R.id.show_transport);
76         mBackupButton = view.findViewById(R.id.backup);
77         mRestoreButton = view.findViewById(R.id.restore);
78 
79         mShowTransportButton.setOnClickListener((v) -> showTransport());
80         mBackupButton.setOnClickListener((v) -> backup());
81         mRestoreButton.setOnClickListener((v) -> restore());
82     }
83 
showTransport()84     private void showTransport() {
85         boolean isEnabled = mBackupManager.isBackupEnabled();
86         Log.v(TAG, "backup is enabled: " + isEnabled);
87         if (!isEnabled) {
88             showMessage("Backup is not enabled yet.\nEnable backup first.");
89             return;
90         }
91         String[] allTransports = mBackupManager.listAllTransports();
92         Log.v(TAG, "All transports: " + Arrays.toString(allTransports));
93         String currentTransport = mBackupManager.getCurrentTransport();
94         Log.v(TAG, "Current Transport:" + currentTransport);
95 
96         StringBuilder sb = new StringBuilder().append("All transports: ");
97         Arrays.stream(allTransports).forEach(t -> sb.append('\n').append(t));
98         sb.append("\nCurrent Transport:\n").append(currentTransport);
99         showMessage(sb.toString());
100     }
101 
backup()102     private void backup() {
103         boolean isEnabled = mBackupManager.isBackupEnabled();
104         Log.v(TAG, "backup is enabled: " + isEnabled);
105         if (!isEnabled) {
106             showMessage("Backup is not enabled yet.\nEnable backup first.");
107             return;
108         }
109 
110         Executors.newSingleThreadExecutor().execute(() -> {
111             backupNow();
112             requireActivity().runOnUiThread(() ->
113                     showMessage("backup is queued, waiting for it to complete."));
114         });
115     }
116 
backupNow()117     private void backupNow() {
118         PackageManager packageManager = getActivity().getPackageManager();
119         List<PackageInfo> installedPackages = null;
120         try {
121             installedPackages = packageManager.getInstalledPackagesAsUser(/* flags= */0, mUserId);
122             Log.v(TAG, "installed packages: " + installedPackages);
123         } catch (Exception e) {
124             Log.e(TAG, "exception in backupNow()", e);
125             return;
126         }
127 
128         if (installedPackages != null) {
129             String[] packages = installedPackages.stream().map(p -> p.packageName)
130                     .toArray(String[]::new);
131 
132             List<String> filteredPackages = new ArrayList<>();
133 
134             for (String p : packages) {
135                 try {
136                     boolean eligible = mBackupManager.isAppEligibleForBackup(p);
137                     Log.v(TAG, "eligible: " + eligible + " package name: " + p);
138                     if (eligible) {
139                         filteredPackages.add(p);
140                         Log.v(TAG, "adding package to filtered packages");
141                     }
142                 } catch (Exception e) {
143                     Log.e(TAG, "isAppEligibleForBackup() cannot connect: ", e);
144                 }
145             }
146             // Currently, the observer is not waiting enough time for the backup to finish
147             // Will implement it later for full functionality
148             int res = mBackupManager.requestBackup(packages, /* observer= */ null);
149             Log.v(TAG, "request backup returned code: " + res);
150             if (res == 0) {
151                 Log.v(TAG, "request backup res successful!");
152             }
153         }
154     }
155 
restore()156     private void restore() {
157         boolean isEnabled = mBackupManager.isBackupEnabled();
158         Log.v(TAG, "backup is enabled: " + isEnabled);
159         if (!isEnabled) {
160             showMessage("Backup is not enabled yet.\nClick enable backup first.");
161             return;
162         }
163 
164         // TODO: use Handler / HandlerThread instead
165         Executors.newSingleThreadExecutor().execute(() -> {
166             restoreNow();
167             requireActivity().runOnUiThread(() -> showMessage("restore is complete"));
168         });
169     }
170 
restoreNow()171     private void restoreNow() {
172         RestoreObserverLocal observer = new RestoreObserverLocal();
173         RestoreSession session = null;
174         try {
175             session = mBackupManager.beginRestoreSession();
176             Log.v(TAG, "current restore session: " + session);
177             if (session != null) {
178                 int err = session.getAvailableRestoreSets(observer);
179                 if (err == 0) {
180                     observer.waitForCompletion();
181                     int restoreResult = session.restoreAll(CURRENT_SET_TOKEN, observer);
182                     Log.v(TAG, "restore all returned code: " + restoreResult);
183                     if (restoreResult == 0) {
184                         Log.i(TAG, "restore successful!!");
185                     }
186                 } else {
187                     Log.v(TAG, "Unable to contact server for restore" + err);
188                 }
189             } else {
190                 Log.i(TAG, "No restore session");
191             }
192         } catch (Exception e) {
193             Log.e(TAG, "exception in beginRestoreSession(): ", e);
194         } finally {
195             if (session != null) {
196                 try {
197                     session.endRestoreSession();
198                 } catch (Exception e) {
199                     Log.w(TAG, "Failed to end the restore session!", e);
200                 }
201             }
202         }
203     }
204 
205     private static final class RestoreObserverLocal extends RestoreObserver {
206         final CountDownLatch mLatch = new CountDownLatch(1);
207 
208         @Override
restoreSetsAvailable(RestoreSet[] result)209         public void restoreSetsAvailable(RestoreSet[] result) {
210             mLatch.countDown();
211         }
212 
waitForCompletion()213         public void waitForCompletion() {
214             boolean received = false;
215             try {
216                 received = mLatch.await(120, TimeUnit.SECONDS);
217             } catch (InterruptedException ex) {
218                 Log.e(TAG, "Current thread is stopped during restore: ", ex);
219                 Thread.currentThread().interrupt();
220             }
221             if (!received) {
222                 Log.w(TAG, "Restore operation is timed out after 120 seconds.");
223             }
224         }
225     }
226 
showMessage(String pattern, Object... args)227     private void showMessage(String pattern, Object... args) {
228         String message = String.format(pattern, args);
229         Log.v(TAG, "showMessage(): " + message);
230         new AlertDialog.Builder(getContext()).setMessage(message).show();
231     }
232 }
233 
234