1 /*
2  * Copyright (C) 2019 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.homepage.contextualcards.logging;
18 
19 import android.util.Log;
20 
21 import androidx.slice.widget.EventInfo;
22 
23 import com.android.settings.homepage.contextualcards.ContextualCard;
24 
25 import java.util.ArrayList;
26 import java.util.List;
27 
28 /**
29  * Utils of building contextual card to string, and parse string back to {@link CardLog}
30  */
31 public class ContextualCardLogUtils {
32 
33     private static final String TAG = "ContextualCardLogUtils";
34 
35     private static final class TapTarget {
36         static int TARGET_DEFAULT = 0;
37         static int TARGET_TITLE = 1;
38         static int TARGET_TOGGLE = 2;
39         static int TARGET_SLIDER = 3;
40     }
41 
42     /**
43      * Log data for a general contextual card event
44      */
45     public static class CardLog {
46         private final String mSliceUri;
47         private final double mRankingScore;
48 
CardLog(Builder builder)49         public CardLog(Builder builder) {
50             mSliceUri = builder.mSliceUri;
51             mRankingScore = builder.mRankingScore;
52         }
53 
getSliceUri()54         public String getSliceUri() {
55             return mSliceUri;
56         }
57 
getRankingScore()58         public double getRankingScore() {
59             return mRankingScore;
60         }
61 
62         public static class Builder {
63             private String mSliceUri;
64             private double mRankingScore;
65 
setSliceUri(String sliceUri)66             public Builder setSliceUri(String sliceUri) {
67                 mSliceUri = sliceUri;
68                 return this;
69             }
70 
setRankingScore(double rankingScore)71             public Builder setRankingScore(double rankingScore) {
72                 mRankingScore = rankingScore;
73                 return this;
74             }
build()75             public CardLog build() {
76                 return new CardLog(this);
77             }
78         }
79     }
80 
81     /**
82      * Log data for a contextual card click event
83      */
84     public static class CardClickLog extends CardLog {
85         private final int mSliceRow;
86         private final int mSliceTapTarget;
87         private final int mUiPosition;
88 
CardClickLog(Builder builder)89         public CardClickLog(Builder builder) {
90             super(builder);
91             mSliceRow = builder.mSliceRow;
92             mSliceTapTarget = builder.mSliceTapTarget;
93             mUiPosition = builder.mUiPosition;
94         }
95 
getSliceRow()96         public int getSliceRow() {
97             return mSliceRow;
98         }
99 
getSliceTapTarget()100         public int getSliceTapTarget() {
101             return mSliceTapTarget;
102         }
103 
getUiPosition()104         public int getUiPosition() {
105             return mUiPosition;
106         }
107 
108         public static class Builder extends CardLog.Builder {
109             private int mSliceRow;
110             private int mSliceTapTarget;
111             private int mUiPosition;
112 
setSliceRow(int sliceRow)113             public Builder setSliceRow(int sliceRow) {
114                 mSliceRow = sliceRow;
115                 return this;
116             }
117 
setSliceTapTarget(int sliceTapTarget)118             public Builder setSliceTapTarget(int sliceTapTarget) {
119                 mSliceTapTarget = sliceTapTarget;
120                 return this;
121             }
122 
setUiPosition(int uiPosition)123             public Builder setUiPosition(int uiPosition) {
124                 mUiPosition = uiPosition;
125                 return this;
126             }
127             @Override
build()128             public CardClickLog build() {
129                 return new CardClickLog(this);
130             }
131         }
132     }
133 
134     /**
135      * Serialize {@link ContextualCard} click event to string
136      *
137      * @param card Clicked Contextual card.
138      * @param sliceRow A Slice can contains multiple row, which row are we clicked
139      * @param tapTarget Integer value of {@link TapTarget}
140      * @param uiPosition Contextual card position in Listview
141      */
buildCardClickLog(ContextualCard card, int sliceRow, int tapTarget, int uiPosition)142     public static String buildCardClickLog(ContextualCard card, int sliceRow, int tapTarget,
143             int uiPosition) {
144         final StringBuilder log = new StringBuilder();
145         log.append(card.getTextSliceUri()).append("|")
146                 .append(card.getRankingScore()).append("|")
147                 .append(sliceRow).append("|")
148                 .append(actionTypeToTapTarget(tapTarget)).append("|")
149                 .append(uiPosition);
150         return log.toString();
151     }
152 
153     /**
154      * Parse string to a {@link CardClickLog}
155      */
parseCardClickLog(String clickLog)156     public static CardClickLog parseCardClickLog(String clickLog) {
157         if (clickLog != null) {
158             final String[] parts = clickLog.split("\\|");
159             if (parts.length < 5) {
160                 return null;
161             }
162             try {
163                 final CardClickLog.Builder builder = new CardClickLog.Builder();
164                 builder.setSliceRow(Integer.parseInt(parts[2]))
165                         .setSliceTapTarget(Integer.parseInt(parts[3]))
166                         .setUiPosition(Integer.parseInt(parts[4]))
167                         .setSliceUri(parts[0])
168                         .setRankingScore(Double.parseDouble(parts[1]));
169                 return builder.build();
170             } catch (Exception e) {
171                 Log.e(TAG, "error parsing log", e);
172                 return null;
173             }
174         }
175         return null;
176     }
177 
178     /**
179      * Serialize {@link ContextualCard} to string
180      *
181      * @param card Contextual card.
182      */
buildCardDismissLog(ContextualCard card)183     public static String buildCardDismissLog(ContextualCard card) {
184         final StringBuilder log = new StringBuilder();
185         log.append(card.getTextSliceUri())
186                 .append("|")
187                 .append(card.getRankingScore());
188         return log.toString();
189     }
190 
191     /**
192      * Parse string to a {@link CardLog}
193      */
parseCardDismissLog(String dismissLog)194     public static CardLog parseCardDismissLog(String dismissLog) {
195         if (dismissLog != null) {
196             final String[] parts = dismissLog.split("\\|");
197             if (parts.length < 2) {
198                 return null;
199             }
200             try {
201                 final CardLog.Builder builder = new CardLog.Builder();
202                 builder.setSliceUri(parts[0])
203                         .setRankingScore(Double.parseDouble(parts[1]));
204                 return builder.build();
205             } catch (Exception e) {
206                 Log.e(TAG, "error parsing log", e);
207                 return null;
208             }
209         }
210         return null;
211     }
212 
213     /**
214      * Serialize List of {@link ContextualCard} to string
215      */
buildCardListLog(List<ContextualCard> cards)216     public static String buildCardListLog(List<ContextualCard> cards) {
217         final StringBuilder log = new StringBuilder();
218         log.append(cards.size());
219         for (ContextualCard card : cards) {
220             log.append("|").append(card.getTextSliceUri())
221                     .append("|").append(card.getRankingScore());
222         }
223         return log.toString();
224     }
225 
226     /**
227      * Parse string to a List of {@link CardLog}
228      */
parseCardListLog(String listLog)229     public static List<CardLog> parseCardListLog(String listLog) {
230         final List<CardLog> logList = new ArrayList<>();
231         try {
232             final String[] parts = listLog.split("\\|");
233             if (Integer.parseInt(parts[0]) < 0) {
234                 return logList;
235             }
236             final int size = parts.length;
237             for (int i = 1; i < size; ) {
238                 final CardLog.Builder builder = new CardLog.Builder();
239                 builder.setSliceUri(parts[i++])
240                         .setRankingScore(Double.parseDouble(parts[i++]));
241                 logList.add(builder.build());
242             }
243         } catch (Exception e) {
244             Log.e(TAG, "error parsing log", e);
245             return logList;
246         }
247         return logList;
248     }
249 
actionTypeToTapTarget(int actionType)250     public static int actionTypeToTapTarget(int actionType) {
251         switch (actionType) {
252             case EventInfo.ACTION_TYPE_CONTENT:
253                 return TapTarget.TARGET_TITLE;
254             case EventInfo.ACTION_TYPE_TOGGLE:
255                 return TapTarget.TARGET_TOGGLE;
256             case EventInfo.ACTION_TYPE_SLIDER:
257                 return TapTarget.TARGET_SLIDER;
258             default:
259                 Log.w(TAG, "unknown type " + actionType);
260                 return TapTarget.TARGET_DEFAULT;
261         }
262     }
263 }
264