• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { backBar } from "../common/components/backBar";
17import { alphabetIndexerComponent } from "../common/components/alphabeticalIndex";
18import { textInput } from "../common/components/search";
19import router from '@ohos.router';
20import bundleManager from "@ohos.bundle.bundleManager";
21import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
22import { BusinessError } from '@ohos.base';
23import audio from '@ohos.multimedia.audio'
24import camera from '@ohos.multimedia.camera'
25import common from '@ohos.app.ability.common';
26import { verifyAccessToken, indexValue, sortByName } from "../common/utils/utils";
27import { ApplicationObj, GroupInfo, routerParams_1, permissionApplications, appInfo } from "../common/utils/typedef";
28import { GlobalContext } from "../common/utils/globalContext";
29import { globalDialog } from "../common/components/dialog";
30import Constants from '../common/utils/constant';
31import { polymorphismGroup, globalGroup, groups } from "../common/model/permissionGroup";
32
33const TAG = 'PermissionManager_MainAbility:';
34const FUZZY_LOCATION_PERMISSION = 'ohos.permission.APPROXIMATELY_LOCATION';
35const PRECISE_LOCATION_PERMISSION = 'ohos.permission.LOCATION';
36let globalIsOn: boolean = (router.getParams() as routerParams_1).globalIsOn; // return title name
37
38@Extend(Image) function customizeImage(width: number, height: number) {
39  .objectFit(ImageFit.Contain)
40  .width(width)
41  .height(height)
42}
43
44@Entry
45@Component
46struct locationInfoPage {
47  private backTitle: ResourceStr = (router.getParams() as routerParams_1).backTitle;
48  private list: permissionApplications[] = (router.getParams() as routerParams_1).list;
49  @State currentGroup: string = GlobalContext.load('currentPermissionGroup');
50  @State polymorphismIsOn: Array<boolean> = [];
51
52  build() {
53    GridRow({ gutter: Constants.GUTTER, columns: {
54      xs: Constants.XS_COLUMNS, sm: Constants.SM_COLUMNS, md: Constants.MD_COLUMNS, lg: Constants.LG_COLUMNS } }) {
55      GridCol({ span: { xs: Constants.XS_SPAN, sm: Constants.SM_SPAN, md: Constants.MD_SPAN, lg: Constants.LG_SPAN },
56        offset: { xs: Constants.XS_OFFSET, sm: Constants.SM_OFFSET, md: Constants.MD_OFFSET, lg: Constants.LG_OFFSET } }) {
57        Row() {
58          Column() {
59            Row() {
60              backBar({ title: JSON.stringify(this.backTitle), recordable: false })
61            }
62            Row() {
63              Column() {
64                applicationItem({ polymorphismIsOn: $polymorphismIsOn })
65
66              }.width(Constants.FULL_WIDTH)
67            }
68            .layoutWeight(Constants.LAYOUT_WEIGHT)
69          }
70        }
71        .height(Constants.FULL_HEIGHT)
72        .width(Constants.FULL_WIDTH)
73        .backgroundColor($r("sys.color.ohos_id_color_sub_background"))
74      }
75    }.backgroundColor($r("sys.color.ohos_id_color_sub_background"))
76  }
77
78  onPageShow() {
79    console.log(TAG + "onPageShow");
80    if (polymorphismGroup.indexOf(this.currentGroup) !== -1) {
81      let bundleNames: string[] = [];
82      this.list.forEach(permissionmanager => {
83        permissionmanager.bundleNames.forEach(bundleName => {
84          if (bundleNames.indexOf(bundleName) == -1) {
85            bundleNames.push(bundleName);
86          }
87        })
88      })
89
90      bundleNames.forEach((bundleName, index) => {
91        bundleManager.getBundleInfo(bundleName, bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION).then(res => {
92          // 0: have permission; -1: no permission
93          this.polymorphismIsOn[index] = true;
94          let reqPermissions: Array<string> = [];
95          res.reqPermissionDetails.forEach(item => {
96            reqPermissions.push(item.name);
97          })
98          for (let j = 0; j < this.list.length; j++) {
99            if ((this.list[j].permission == PRECISE_LOCATION_PERMISSION) && (res.targetVersion >= Constants.API_VERSION_SUPPORT_STAGE)) {
100              continue;
101            }
102            if ((this.list[j].permission == FUZZY_LOCATION_PERMISSION) && (res.targetVersion < Constants.API_VERSION_SUPPORT_STAGE)) {
103              continue;
104            }
105            if (reqPermissions.indexOf(this.list[j].permission) == -1) {
106              continue;
107            }
108            verifyAccessToken(res.appInfo.accessTokenId, this.list[j].permission).then((access) => {
109              if (Number(access) === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) {
110                this.polymorphismIsOn[index] = false;
111              }
112            });
113          }
114        }).catch((error: BusinessError) => {
115          console.log(TAG + bundleName + "onPageShow getBundleInfo failed, cause: " + JSON.stringify(error));
116        })
117      })
118    }
119  }
120}
121
122@Component
123struct applicationItem {
124  private context = getContext(this) as common.UIAbilityContext;
125  private backTitle: ResourceStr = (router.getParams() as routerParams_1).backTitle;
126  private list: permissionApplications[] = (router.getParams() as routerParams_1).list;
127  @State permissionNum: number = Constants.PERMISSION_NUM; // permission num
128  @State toggleIsOn: boolean[] = []; // toggle switch state array
129  @State isRisk: boolean[] = [];
130  @State applicationList: ApplicationObj[] = []; // application info array
131  @State searchResult: boolean = true; // search results
132  @Link polymorphismIsOn: Array<boolean>;
133  @State globalIsOn: boolean = true;
134  @State selectedIndex: number = 0;
135  @State isTouch: string = '';
136  @State groupInfo: GroupInfo = new GroupInfo('', '', '', '', [], '', [], false);
137  @State currentGroup: string = GlobalContext.load('currentPermissionGroup');
138  @State isMuteSupported: boolean = GlobalContext.load('isMuteSupported');
139  @State allBundleInfo: appInfo[] = GlobalContext.load('allBundleInfo');
140  scroller: Scroller = new Scroller();
141
142  privacyDialogController: CustomDialogController = new CustomDialogController({
143    builder: globalDialog({ globalIsOn: $globalIsOn }),
144    autoCancel: false,
145    alignment: DialogAlignment.Center,
146    customStyle: true
147  })
148
149  @Builder ListItemLayout(item: ApplicationObj) {
150    ListItem() {
151      Row() {
152        Column() {
153          Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
154            Row() {
155              Image(item.icon)
156                .customizeImage(Constants.AUTHORITY_IMAGE_WIDTH, Constants.AUTHORITY_IMAGE_HEIGHT)
157                .margin({ right: Constants.AUTHORITY_IMAGE_MARGIN_RIGHT })
158              Column() {
159                Text(item.label)
160                  .width(Constants.MAXIMUM_HEADER_WIDTH)
161                  .maxLines(Constants.MAXIMUM_HEADER_LINES)
162                  .textOverflow({ overflow: TextOverflow.Ellipsis })
163                  .fontSize(Constants.TEXT_MIDDLE_FONT_SIZE)
164                  .fontWeight(FontWeight.Medium)
165                  .fontColor($r('sys.color.ohos_id_color_text_primary'))
166                if (this.isRisk[item.index]) {
167                  Text($r('app.string.risk_warning'))
168                    .fontSize(Constants.TEXT_SMALL_FONT_SIZE)
169                    .fontColor($r('sys.color.ohos_id_color_text_secondary'))
170                }
171              }.flexGrow(Constants.FLEX_GROW)
172              .alignItems(HorizontalAlign.Start)
173              if (polymorphismGroup.indexOf(this.currentGroup) == -1) {
174                Toggle({ type: ToggleType.Switch, isOn: this.toggleIsOn[item.index] })
175                  .selectedColor($r('sys.color.ohos_id_color_toolbar_icon_actived'))
176                  .switchPointColor($r('sys.color.ohos_id_color_foreground_contrary'))
177                  .padding({ right: 0 })
178                  .width(Constants.AUTHORITY_TOGGLE_WIDTH)
179                  .height(Constants.AUTHORITY_TOGGLE_HEIGHT)
180                  .onChange((isOn: boolean) => {
181                    if (item.permission === undefined) {
182                      return;
183                    }
184                    let _this = this;
185                    if (isOn) {
186                      let promises = this.list.map(it => new Promise<number>((resolve) => {
187                        _this.grantUserGrantedPermission(item.accessTokenId, it.permission, resolve);
188                      }));
189                      Promise.all(promises).then(() => {
190                        _this.toggleIsOn[item.index] = true;
191                        let num = _this.toggleIsOn.filter(item => item === true).length;
192                        _this.permissionNum = num;
193                      });
194                    } else {
195                      let promises = this.list.map(it => new Promise<number>((resolve) => {
196                        _this.revokeUserGrantedPermission(item.accessTokenId, it.permission, resolve);
197                      }));
198                      Promise.all(promises).then(() => {
199                        _this.toggleIsOn[item.index] = false;
200                        let num = _this.toggleIsOn.filter(item => item === true).length;
201                        _this.permissionNum = num;
202                      });
203                    }
204                  })
205              } else {
206                Text(this.polymorphismIsOn[item.index] ? $r('app.string.allow') : $r('app.string.ban'))
207                .fontSize(Constants.TEXT_SMALL_FONT_SIZE)
208                .fontColor($r('sys.color.ohos_id_color_text_secondary'))
209                .margin({ right: Constants.AUTHORITY_IMAGE_MARGIN_RIGHT })
210                Image($r('app.media.ic_public_arrow_right'))
211                  .fillColor($r('sys.color.ohos_id_color_tertiary'))
212                  .customizeImage(Constants.IMAGE_WIDTH, Constants.IMAGE_HEIGHT)
213              }
214            }
215            .width(Constants.FULL_WIDTH)
216            .height(Constants.AUTHORITY_ROW_HEIGHT)
217            .constraintSize({ minHeight: Constants.AUTHORITY_CONSTRAINTSIZE_MINHEIGHT })
218          }
219        }.onClick(() => {
220          if (polymorphismGroup.indexOf(this.currentGroup) !== -1) {
221            let permissions: string[] = [];
222            this.list.forEach(item => {
223              permissions.push(item.permission);
224            })
225            this.allBundleInfo.forEach(bundleInfo => {
226              if (bundleInfo.bundleName === item.bundleName) {
227                GlobalContext.store('applicationInfo', bundleInfo);
228              }
229            })
230            router.pushUrl({
231              url: 'pages/application-tertiary',
232              params: {
233                bundleName: item.bundleName,
234                backTitle: this.backTitle,
235                permission: permissions,
236                status: this.polymorphismIsOn[item.index] ? Constants.RADIO_ALLOW_INDEX : Constants.RADIO_BAN_INDEX
237              }
238            });
239          }
240        })
241      }
242    }.padding({ left: $r('sys.float.ohos_id_card_margin_start'), right: $r('sys.float.ohos_id_card_margin_end') })
243    .enabled(!this.isRisk[item.index])
244    .opacity(this.isRisk[item.index] ? $r('sys.float.ohos_id_alpha_disabled') : 1)
245    .borderRadius($r("sys.float.ohos_id_corner_radius_default_l"))
246    .linearGradient((this.isTouch === item.bundleName) ? {
247        angle: 90,
248        direction: GradientDirection.Right,
249        colors: [['#DCEAF9', 0.0], ['#FAFAFA', 1.0]]
250      } : {
251        angle: 90,
252        direction: GradientDirection.Right,
253        colors: [[$r("sys.color.ohos_id_color_list_card_bg"), 1], [$r("sys.color.ohos_id_color_list_card_bg"), 1]]
254      })
255    .onTouch(event => {
256      if (event === undefined) {
257        return;
258      }
259      if (event.type === TouchType.Down && polymorphismGroup.indexOf(this.currentGroup) !== -1) {
260        this.isTouch = item.bundleName ? item.bundleName : '';
261      }
262      if (event.type === TouchType.Up) {
263        this.isTouch = '';
264      }
265    })
266  }
267
268  /**
269   * Take the total number of access applications
270   */
271  getGrantApplicationNumber() {
272    if (polymorphismGroup.indexOf(this.currentGroup) !== -1) {
273      let sum = this.polymorphismIsOn.filter(item => item == true);
274      return sum.length;
275    } else {
276      return this.permissionNum;
277    }
278  }
279
280  /**
281   * Grant permissions to the app
282   * @param {Number} accessTokenId
283   * @param {String} permission permission name
284   * @param {Number} index Array index to modify permission status
285   */
286  grantUserGrantedPermission(accessTokenId: number, permission: Permissions, resolve: (value: number) => void) {
287    abilityAccessCtrl.createAtManager().grantUserGrantedPermission(accessTokenId, permission, Constants.PERMISSION_FLAG).then(() => {
288      resolve(0);
289    }).catch((error: BusinessError) => {
290      resolve(-1);
291      console.error(TAG + 'abilityAccessCtrl.createAtManager.grantUserGrantedPermission failed. Cause: ' + JSON.stringify(error));
292    })
293  }
294
295  /**
296   * Deauthorize the app
297   * @param {Number} accessTokenId
298   * @param {String} permission permission name
299   * @param {Number} index Array index to modify permission status
300   */
301  revokeUserGrantedPermission(accessTokenId: number, permission: Permissions, resolve: (value: number) => void) {
302    abilityAccessCtrl.createAtManager().revokeUserGrantedPermission(accessTokenId, permission, Constants.PERMISSION_FLAG).then(() => {
303      resolve(0);
304    }).catch((error: BusinessError) => {
305      resolve(-1);
306      console.error(TAG + 'abilityAccessCtrl.createAtManager.revokeUserGrantedPermission failed. Cause: ' + JSON.stringify(error));
307    })
308  }
309
310  /**
311   * Lifecycle function, executed when the page is initialized
312   */
313  aboutToAppear() {
314    let bundleNames: string[] = [];
315    this.applicationList = [];
316    this.list.forEach(permissionmanager => {
317      permissionmanager.bundleNames.forEach(bundleName => {
318        if (bundleNames.indexOf(bundleName) == -1) {
319          bundleNames.push(bundleName);
320        }
321      })
322    })
323    groups.forEach(group => {
324      if (group.name === this.currentGroup) {
325        this.groupInfo = group;
326      }
327    })
328
329    let atManager = abilityAccessCtrl.createAtManager();
330    for (let i = 0; i < bundleNames.length; i++) {
331      // Get BundleInfo based on bundle name
332      this.allBundleInfo.forEach(bundleInfo => {
333        if (bundleInfo.bundleName === bundleNames[i]) {
334          this.applicationList.push(
335            new ApplicationObj(
336              bundleInfo.label,
337              bundleInfo.icon,
338              i,
339              bundleInfo.tokenId,
340              this.list[0].permission,
341              bundleInfo.zhTag,
342              bundleInfo.indexTag,
343              bundleInfo.language,
344              bundleInfo.bundleName) // Get the first letter in the returned initials array
345          );
346          this.isRisk[i] = false;
347          try {
348            atManager.getPermissionFlags(bundleInfo.tokenId, this.list[0].permission).then(data => {
349              if (data == Constants.PERMISSION_POLICY_FIXED) {
350                this.isRisk[i] = true;
351              }
352            })
353          }
354          catch(err) {
355            console.log(TAG + 'getPermissionFlags error: ' + JSON.stringify(err));
356          }
357          // 0: have permission; -1: no permission
358          let boole = true;
359          this.permissionNum++;
360          for (let j = 0; j < this.list.length; j++) {
361            if (bundleInfo.permissions.indexOf(this.list[j].permission) == -1) {
362              continue;
363            }
364            verifyAccessToken(bundleInfo.tokenId, this.list[j].permission).then((access) => {
365              if (Number(access) === Constants.PERMISSION_INDEX) {
366                if (boole) {
367                  this.toggleIsOn[i] = true;
368                }
369              } else {
370                if (boole) {
371                  this.permissionNum--;
372                }
373                boole = false;
374                this.toggleIsOn[i] = false;
375              }
376            });
377          }
378        }
379      })
380    }
381    if (globalGroup.indexOf(this.currentGroup) !== -1) {
382      this.globalIsOn = globalIsOn;
383      if (this.currentGroup == "CAMERA") {
384        let cameraManager = camera.getCameraManager(GlobalContext.load('context'));
385        cameraManager.on('cameraMute', (err, curMuted) => {
386          console.log(TAG + 'curMuted: ' + JSON.stringify(curMuted) + ' err: ' + JSON.stringify(err));
387          this.globalIsOn = !curMuted;
388        })
389      } else {
390        let audioManager = audio.getAudioManager();
391        let audioVolumeManager = audioManager.getVolumeManager();
392        let groupid = audio.DEFAULT_VOLUME_GROUP_ID;
393        audioVolumeManager.getVolumeGroupManager(groupid).then(audioVolumeGroupManager => {
394          audioVolumeGroupManager.on('micStateChange', micStateChange => {
395            this.globalIsOn = !micStateChange.mute;
396          })
397        })
398      }
399    }
400  }
401
402  build() {
403    Column() {
404      Row() {
405        textInput({
406          applicationItem: $applicationList,
407          searchResult: $searchResult
408        })
409      }.padding({
410        left: Constants.AUTHORITY_TEXTINPUT_PADDING_LEFT,
411        right: Constants.AUTHORITY_TEXTINPUT_PADDING_RIGHT
412      })
413      Flex({ alignItems:ItemAlign.Start, justifyContent: FlexAlign.Start }) {
414        Column() {
415          if (globalGroup.indexOf(this.currentGroup) !== -1  && this.isMuteSupported === true) {
416            Row() {
417              Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
418                Text(this.currentGroup == "CAMERA" ? $r('app.string.camera') : $r('app.string.microphone'))
419                  .fontSize(Constants.TEXT_MIDDLE_FONT_SIZE).fontColor($r('sys.color.ohos_id_color_text_primary'))
420                  .fontWeight(FontWeight.Medium)
421                Row() {
422                  Toggle({ type: ToggleType.Switch, isOn: this.globalIsOn })
423                    .selectedColor($r('sys.color.ohos_id_color_toolbar_icon_actived'))
424                    .switchPointColor($r('sys.color.ohos_id_color_foreground_contrary'))
425                    .padding({ right: 0 })
426                    .onChange((isOn: boolean) => {
427                      if (isOn) {
428                        if (this.currentGroup == "CAMERA") {
429                          let cameraManager = camera.getCameraManager(GlobalContext.load('context'));
430                          cameraManager.muteCamera(false);
431                        } else {
432                          let audioManager = audio.getAudioManager();
433                          let audioVolumeManager = audioManager.getVolumeManager();
434                          let groupid = audio.DEFAULT_VOLUME_GROUP_ID;
435                          audioVolumeManager.getVolumeGroupManager(groupid).then(audioVolumeGroupManager => {
436                            audioVolumeGroupManager.setMicrophoneMute(false)
437                          })
438                        }
439                      }
440                    })
441                  Row().onClick(() => { this.privacyDialogController.open() })
442                    .width(Constants.DEFAULT_SLIDER_WIDTH).height(Constants.DEFAULT_SLIDER_HEIGHT)
443                    .position({ x: this.globalIsOn ? 0 : Constants.OFFSET, y: 0 })
444                }.clip(true)
445              }.height(Constants.LISTITEM_ROW_HEIGHT)
446              .padding({ left: Constants.DEFAULT_PADDING_START, right: Constants.DEFAULT_PADDING_END })
447            }.padding({ top: Constants.LIST_PADDING_TOP, bottom: Constants.LIST_PADDING_BOTTOM })
448            .backgroundColor($r('sys.color.ohos_id_color_list_card_bg'))
449            .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
450            .margin({ top: Constants.TERTIARY_ROW_MARGIN_TOP })
451          }
452          Flex({ justifyContent: FlexAlign.Start }) {
453            if (this.globalIsOn) {
454              if (this.getGrantApplicationNumber() > 0) {
455                Text() {
456                  Span(this.groupInfo.enable_description_start ? this.groupInfo.enable_description_start : '')
457                  Span(String(this.getGrantApplicationNumber()))
458                  Span(this.groupInfo.enable_description_end ? this.groupInfo.enable_description_end : '')
459                }
460                  .fontSize(Constants.TEXT_SMALL_FONT_SIZE)
461                  .fontColor($r('sys.color.ohos_id_color_text_secondary'))
462                  .margin({ top: Constants.AUTHORITY_TEXT_MARGIN_TOP })
463              } else {
464                Text(this.groupInfo.forbidden_description)
465                  .fontSize(Constants.TEXT_SMALL_FONT_SIZE)
466                  .fontColor($r('sys.color.ohos_id_color_text_secondary'))
467                  .margin({ top: Constants.AUTHORITY_TEXT_MARGIN_TOP })
468              }
469            } else {
470              Text(this.currentGroup == "CAMERA" ? $r('app.string.camera_is_off') : $r('app.string.microphone_is_off'))
471                .fontSize(Constants.TEXT_SMALL_FONT_SIZE)
472                .fontColor($r('sys.color.ohos_id_color_text_secondary'))
473                .margin({ top: Constants.AUTHORITY_TEXT_MARGIN_TOP })
474            }
475          }.padding({ left: Constants.DEFAULT_PADDING_START, right: Constants.DEFAULT_PADDING_END })
476          .margin({ bottom: Constants.AUTHORITY_ROW_MARGIN_BOTTOM })
477          Row() {
478            Column() {
479              if (!this.applicationList.length) {
480                if (this.searchResult) {
481                  Row() {}
482                } else {
483                  Row() {
484                    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
485                      Image($r('app.media.searchnoresult'))
486                        .customizeImage(Constants.SEARCHNORESULT_IMAGE_WIDTH, Constants.SEARCHNORESULT_IMAGE_HEIGHT)
487                    }
488                  }
489                }
490              } else {
491                Row() {
492                  List({ scroller: this.scroller }) {
493                    ForEach(sortByName(this.applicationList), (item: ApplicationObj) => {
494                      this.ListItemLayout(item)
495                    }, (item: ApplicationObj) => JSON.stringify(item))
496                  }
497                  .backgroundColor($r('sys.color.ohos_id_color_list_card_bg'))
498                  .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
499                  .padding(Constants.LIST_PADDING_TOP)
500                  .divider({
501                    strokeWidth: Constants.DIVIDER,
502                    color: $r('sys.color.ohos_id_color_list_separator'),
503                    startMargin: Constants.DIVIDER_MARGIN_RIGHT_APPLICATION,
504                    endMargin: Constants.DEFAULT_MARGIN_END
505                  })
506                  .onScrollIndex((start, end) => {
507                    GlobalContext.getContext().set('scroller', this.scroller);
508                    if (this.applicationList.length > 0) {
509                      let alphabeticalIndex: string = sortByName(this.applicationList)[start].indexTag;
510                      let index = indexValue.indexOf(alphabeticalIndex);
511                      this.selectedIndex = index >= 0 ? index : 0;
512                    }
513                  })
514                }
515              }
516            }.width(Constants.FULL_WIDTH)
517            .margin({ bottom: globalGroup.includes(this.currentGroup) && this.isMuteSupported === true ? Constants.AUTHORITY_LIST_MARGIN_BOTTOM_GLOBAL : Constants.AUTHORITY_LIST_MARGIN_BOTTOM })
518          }
519        }.padding({ left: Constants.AUTHORITY_LISTITEM_PADDING_LEFT })
520        Column() {
521          alphabetIndexerComponent({ applicationItem: $applicationList, index: $selectedIndex })
522        }.width(Constants.AUTHORITY_ALPHABETINDEX_WIDTH)
523         .padding({ top: Constants.AUTHORITY_ALPHABETINDEX_PADDING_TOP })
524        .margin({ bottom: Constants.APPLICATION_LIST_MARGIN_BOTTOM })
525      }.flexGrow(Constants.FLEX_GROW)
526    }.height(Constants.FULL_HEIGHT)
527  }
528}
529