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.android.sample.rollbackapp;
18 
19 import static android.app.PendingIntent.FLAG_MUTABLE;
20 
21 import android.app.Activity;
22 import android.app.PendingIntent;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.rollback.PackageRollbackInfo;
28 import android.content.rollback.RollbackInfo;
29 import android.content.rollback.RollbackManager;
30 import android.os.Bundle;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.BaseAdapter;
35 import android.widget.Button;
36 import android.widget.CheckBox;
37 import android.widget.ListView;
38 import android.widget.TextView;
39 import android.widget.Toast;
40 
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.List;
44 
45 public class MainActivity extends Activity {
46 
47     List<Integer> mIdsToRollback = new ArrayList<>();
48     Button mTriggerRollbackButton;
49     RollbackManager mRollbackManager;
50     static final String ROLLBACK_ID_EXTRA = "rollbackId";
51     static final String ACTION_NAME = MainActivity.class.getName();
52 
53     @Override
onCreate(Bundle savedInstanceState)54     public void onCreate(Bundle savedInstanceState) {
55         super.onCreate(savedInstanceState);
56         setContentView(R.layout.activity_main);
57         ListView rollbackListView = findViewById(R.id.listView);
58         mRollbackManager = getApplicationContext().getSystemService(RollbackManager.class);
59         initTriggerRollbackButton();
60 
61         // Populate list of available rollbacks.
62         List<RollbackInfo> availableRollbacks = mRollbackManager.getAvailableRollbacks();
63         CustomAdapter adapter = new CustomAdapter(availableRollbacks);
64         rollbackListView.setAdapter(adapter);
65 
66         // Register receiver for rollback status events.
67         getApplicationContext().registerReceiver(
68                 new BroadcastReceiver() {
69                     @Override
70                     public void onReceive(Context context,
71                             Intent intent) {
72                         int rollbackId = intent.getIntExtra(ROLLBACK_ID_EXTRA, -1);
73                         int rollbackStatusCode = intent.getIntExtra(RollbackManager.EXTRA_STATUS,
74                                 RollbackManager.STATUS_FAILURE);
75                         String rollbackStatus = "FAILED";
76                         if (rollbackStatusCode == RollbackManager.STATUS_SUCCESS) {
77                             rollbackStatus = "SUCCESS";
78                             mTriggerRollbackButton.setClickable(false);
79                         }
80                         makeToast("Status for rollback ID " + rollbackId + " is " + rollbackStatus);
81                     }}, new IntentFilter(ACTION_NAME), Context.RECEIVER_NOT_EXPORTED);
82     }
83 
initTriggerRollbackButton()84     private void initTriggerRollbackButton() {
85         mTriggerRollbackButton = findViewById(R.id.trigger_rollback_button);
86         mTriggerRollbackButton.setClickable(false);
87         mTriggerRollbackButton.setOnClickListener(v -> {
88             // Commits all selected rollbacks. Rollback status events will be sent to our receiver.
89             for (int i = 0; i < mIdsToRollback.size(); i++) {
90                 Intent intent = new Intent(ACTION_NAME);
91                 intent.putExtra(ROLLBACK_ID_EXTRA, mIdsToRollback.get(i));
92                 intent.setPackage(getApplicationContext().getPackageName());
93                 PendingIntent pendingIntent = PendingIntent.getBroadcast(
94                         getApplicationContext(), 0, intent, FLAG_MUTABLE);
95                 mRollbackManager.commitRollback(mIdsToRollback.get(i),
96                         Collections.emptyList(),
97                         pendingIntent.getIntentSender());
98             }
99         });
100     }
101 
102 
103 
makeToast(String message)104     private void makeToast(String message) {
105         runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show());
106     }
107 
108     public class CustomAdapter extends BaseAdapter {
109         List<RollbackInfo> mRollbackInfos;
110         LayoutInflater mInflater = LayoutInflater.from(getApplicationContext());
111 
CustomAdapter(List<RollbackInfo> rollbackInfos)112         CustomAdapter(List<RollbackInfo> rollbackInfos) {
113             mRollbackInfos = rollbackInfos;
114         }
115 
116         @Override
getCount()117         public int getCount() {
118             return mRollbackInfos.size();
119         }
120 
121         @Override
getItem(int position)122         public Object getItem(int position) {
123             return mRollbackInfos.get(position);
124         }
125 
126         @Override
getItemId(int position)127         public long getItemId(int position) {
128             return mRollbackInfos.get(position).getRollbackId();
129         }
130 
131         @Override
getView(int position, View view, ViewGroup parent)132         public View getView(int position, View view, ViewGroup parent) {
133             if (view == null) {
134                 view = mInflater.inflate(R.layout.listitem_rollbackinfo, null);
135             }
136             RollbackInfo rollbackInfo = mRollbackInfos.get(position);
137             TextView rollbackIdView = view.findViewById(R.id.rollback_id);
138             rollbackIdView.setText("Rollback ID " + rollbackInfo.getRollbackId());
139             TextView rollbackPackagesTextView = view.findViewById(R.id.rollback_packages);
140             StringBuilder sb = new StringBuilder();
141             for (int i = 0; i < rollbackInfo.getPackages().size(); i++) {
142                 PackageRollbackInfo pkgInfo = rollbackInfo.getPackages().get(i);
143                 sb.append(pkgInfo.getPackageName() + ": "
144                         + pkgInfo.getVersionRolledBackFrom().getLongVersionCode() + " -> "
145                         + pkgInfo.getVersionRolledBackTo().getLongVersionCode() + ",");
146             }
147             sb.deleteCharAt(sb.length() - 1);
148             rollbackPackagesTextView.setText(sb.toString());
149             CheckBox checkbox = view.findViewById(R.id.checkbox);
150             checkbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
151                 if (isChecked) {
152                     mIdsToRollback.add(rollbackInfo.getRollbackId());
153                 } else {
154                     mIdsToRollback.remove(Integer.valueOf(rollbackInfo.getRollbackId()));
155                 }
156                 mTriggerRollbackButton.setClickable(mIdsToRollback.size() > 0);
157             });
158             return view;
159         }
160     }
161 }
162